#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
enum DHT_Type {
DHT22,
DHT11
};
// Escolher o tipo de sensor no circuito
#define _type DHT22
// Botões e led
#define BOTAO_RED PD2
#define BOTAO_BLUE PD1
#define BOTAO_YELL PD0
#define LED PE4
#define PWM_LED_DES 0
#define PWM_LED_BAIXA 100
#define PWM_LED_ALTA 255
// Variáveis importantes
volatile int tem_led = 0; // Variável para dizer se vai ter ou não
float med[10] = {0}; // Vetor para armazenar as 10 últimas medições
int index = 0; // Variável não está mais sendo utilizada, era para as posições do vetor da média das 10 medições
float sum = 0; // Variável para armazenar a soma dos 10 elementos do vetor
float average = 0; // Variável para armazenar a média dos 10 elementos do vetor
float temperature; // Variável para temperatura atual
uint8_t temperature_decimal; // Decimal da temp atual || não está mais sendo utilizada pois foi encontrada uma função para transformar valores em strings direto
float temp_min = 36; // Mínimo inicial padrão
float temp_max = 37; // Máximo inicial padrão
volatile int tela_atual = 0; // Estado da tela || não está mais sendo utilizada, pois tem somente uma tela atualmente
volatile bool flag = false; // Flag para debounce do botão
// ========================================================================
// PINOS LCD
// ========================================================================
// LCD - Pinos de controle
#define CONTR_LCD PORTA
#define RS PA1
//#define RW PA3
#define E PA5
// LCD - Pinos de dados (8 bits)
#define DADOS_LCD PORTC
#define nibble_dados 1 // Controla a ordem em que os dados vão ser enviados para o LCD | 1 - Mais,menos signif | 0 - Menos,mais signifi
// Definição do Sensor DHT
#define DHT_PIN PH5
// set_bit(CONTR_LCD, E);: Define o bit de controle E (Enable) do LCD como 1, indicando que um comando ou dado está sendo enviado.
// clr_bit(CONTR_LCD, E);: Limpa o bit de controle E do LCD, retornando-o para 0.
#define pulse_enable() _delay_us(1); set_bit(CONTR_LCD, E); _delay_us(1); clr_bit(CONTR_LCD, E); _delay_us(45);
// ========================================================================
// Macros para o trabalho com bits
// ========================================================================
#define set_bit(y,bit) (y|=(1<<bit)) // Coloca em 1 o bit x da variável Y
#define clr_bit(y,bit) (y&=~(1<<bit)) // Coloca em 0 o bit x da variável Y
#define cpl_bit(y,bit) (y^=(1<<bit)) // Troca o estado lógico do bit x da variável Y
#define tst_bit(y,bit) (y&(1<<bit)) // Retorna 0 ou 1 conforme leitura do bit
// Ler a temperatura do sensor
uint8_t dht_read(uint8_t dht, enum DHT_Type type, int tem_led);
// Ligar/desligar led
void led_control(int temperature);
// ERA auxiliar para fazer a leitura de bits dos dados enviados pelo sensor
uint8_t dht_mount_byte()
{
uint8_t b = 0x0;
for (uint8_t i = 0; i < 8; i++)
{
while(~(PINH >> DHT_PIN) & 0x1);
switch(_type)
{
case DHT22: // DHT22 espera 50us
_delay_us(50);
case DHT11: // DHT11 espera 30us
_delay_us(30);
}
if ((PINH >> DHT_PIN) & 0x1) b = (b<<1)|(0x01);
else b = (b<<1);
while((PINH >> DHT_PIN) & 0x1);
}
return b;
}
// ========================================================================
// LCD - Enviar caracteres/instruções
// ========================================================================
void lcd_cmd(unsigned char c, char cd)
{
// cd : indica se instrução (0) ou caractere (1)
if(cd == 0)
clr_bit(CONTR_LCD,RS);
else
set_bit(CONTR_LCD,RS);
// Primeiro nibble de dados - 4 MSB
#if (nibble_dados)
// Compila o código para os pinos de dados do LCD nos 4 MSB do PORT
DADOS_LCD = (DADOS_LCD & 0x0F) | (0xF0 & c);
#else
// Compila o código para os pinos de dados do LCD nos 4 LSB do PORT
DADOS_LCD = (DADOS_LCD & 0xF0) | (c >> 4);
#endif
pulse_enable();
// Segundo nibble de dados - 4 LSB
#if (nibble_dados)
// Compila o código para os pinos de dados do LCD nos 4 MSB do PORT
DADOS_LCD = (DADOS_LCD & 0x0F) | (0xF0 & (c << 4));
#else
// Compila o código para os pinos de dados do LCD nos 4 LSB do PORT
DADOS_LCD = (DADOS_LCD & 0xF0) | (0x0F & c);
#endif
pulse_enable();
// Se for instrução de retorno ou limpeza espera LCD estar pronto
if((cd == 0) && (c < 4)) _delay_ms(2);
}
// ========================================================================
// LCD - Inicializacao do LCD com via de dados de 4 Bits
// ========================================================================
void lcd_init_4bits()
{
// Como o LCD será só escrito, R/W é sempre zero.
clr_bit(CONTR_LCD,RS); // RS em zero indicando que o dado para o LCD será uma instrução
clr_bit(CONTR_LCD,E); // Pino de habilitação em zero
// Tempo para estabilizar a tensão do LCD, após VCC ultrapassar 4.5 V (na prática pode ser maior)
_delay_ms(20);
// Interface de 8 bits
#if (nibble_dados)
DADOS_LCD = (DADOS_LCD & 0x0F) | 0x30;
#else
DADOS_LCD = (DADOS_LCD & 0xF0) | 0x03;
#endif
pulse_enable(); // Habilitação respeitando os tempos de resposta do LCD
_delay_ms(5);
pulse_enable();
_delay_us(200);
pulse_enable();
// Interface de 4 bits
// Deve ser enviado duas vezes (a outra está abaixo)
#if (nibble_dados)
DADOS_LCD = (DADOS_LCD & 0x0F) | 0x20;
#else
DADOS_LCD = (DADOS_LCD & 0xF0) | 0x02;
#endif
pulse_enable();
// Interface de 4 bits 2 linhas
// Aqui se habilita as 2 linhas
lcd_cmd(0x28,0);
// São enviados os 2 nibbles (0x2 e 0x8)
lcd_cmd(0x08,0); // Desliga o display
lcd_cmd(0x01,0); // Limpa todo o display
lcd_cmd(0x0C,0); // Mensagem aparente cursor inativo não piscando
lcd_cmd(0x80,0); // Inicializa cursor na primeira posição a esquerda
}
// ========================================================================
// LCD - Escrita no LCD
// ========================================================================
void lcd_write(char *c)
{
for (; *c!=0;c++) lcd_cmd(*c,1);
}
int main()
{
// Serial.begin(9600);
// Configurar portas de saida do LCD
DDRA = 0xFF; // Configura todas as portas A como saída para o LCD
DDRC = 0xFF; // Configura todas as portas C como saída para o LCD
DDRE |= (1 << LED); // Configura o pino PG2 como saída para o LED
// Configuração do Timer1 para modo Fast PWM (com OCR1B para controle do LED)
TCCR3A |= (1 << COM3B1) | (1 << WGM30); // Configuração para modo Fast PWM, Clear OC1B on Compare Match, set OC1B at BOTTOM
TCCR3B |= (1 << CS30) | (1 << CS32); // Configuração para modo Fast PWM, prescaler 1024
OCR3B = 0; // Inicializa OCR1B com 0 (LED desligado)
DDRD &= ~(1 << BOTAO_BLUE); // Define o pino do botão como Entrada
PORTD |= (1 << BOTAO_BLUE); // Ativa o pull-up interno do botão
// DDRD &= ~(1 << BOTAO_RED); // Define o pino do botão como Entrada
// PORTD |= (1 << BOTAO_RED); // Ativa o pull-up interno do botão
// DDRD &= ~(1 << BOTAO_YELL); // Define o pino do botão como Entrada
// PORTD |= (1 << BOTAO_YELL); // Ativa o pull-up interno do botão
// Configuração de interrupção externa INT1 (PD1) - Botão de Zerar
EICRA |= (1 << ISC10); // Configura a interrupção para acontecer na borda de descida
EIMSK |= (1 << INT1); // Habilita a interrupção INT1
// Configuração do Timer1 para modo CTC (Clear Timer on Compare Match)
TCCR1B |= (1 << WGM12); // Ativa modo CTC
OCR1A = 0xF424; // Valor de comparação para 1 segundo (frequência de 16 MHz)
TIMSK1 |= (1 << OCIE1A); // Habilita interrupção do comparador A
TCCR1B |= (1 << CS12) | (1 << CS10); // Configura prescaler para 1024 e inicia a contagem
sei(); // Habilita todas as interrupções globais
// Inicializar LCD
lcd_init_4bits();
// Tela inicial por 2 segundos
lcd_cmd(0x01, 0); // Limpa a tela
_delay_ms(2);
lcd_cmd(0x80, 0); // Cursor na primeira linha
lcd_write("P&R's Tech");
_delay_ms(500);
lcd_cmd(0xC0, 0); // Cursor na segunda linha
lcd_write("For your child");
_delay_ms(2000); // Tela permanece por 2s
tela_atual = 1; // Segue para tela principal
while (1)
{
if (tela_atual == 1)
{
int result = dht_read(DHT_PIN, _type, tem_led);
if (result != 0)
{
lcd_cmd(0x01, 0); // Limpa o display
_delay_ms(2);
lcd_cmd(0x80, 0); // Posiciona o cursor na primeira linha
lcd_write("Erro: ");
// char buffer[2];
// itoa(result, buffer, 10);
char erroString[16]={0};
dtostrf(result, 6, 2, erroString);
lcd_write(erroString);
}
_delay_ms(1000); // Aguarda 1 segundo antes de fazer a próxima leitura
flag = false;
}
}
}
ISR(INT1_vect) {
_delay_ms(100);
// Se botão for pressionado
if( (tela_atual == 1) & (flag == false))
{
// Enquanto o botão não for pressionado
// fique preso nesse loop
while (!(PIND & (1 << BOTAO_BLUE)));
// Mude o estado se tem ou não led
tem_led = !tem_led;
// Serial.println(tela_atual);
flag = true;
}
}
ISR(TIMER1_COMPA_vect)
{
// Deslocamento circular para a esquerda
for(int i = 0; i < 9; i++) {
med[i] = med[i + 1];
}
// O último elemento do vetor é atualizado no loop
// da main, dentro da função dht_read
// Recalcula a média das 10 últimas medições
sum = 0;
for(int i = 0; i < 10; i++) {
sum += med[i];
}
average = sum / 10;
//Serial.print(med[9]);
}
// Função para controlar o LED com base na temperatura
void led_control(float temperature)
{
if ( (temperature >= (temp_min - 3) && temperature <= temp_min) ||
(temperature <= (temp_max + 3) && temperature >= temp_max) ) {
// Temperatura dentro do intervalo, usar a intensidade baixa
OCR3B = PWM_LED_BAIXA;
} else if (temperature < (temp_min - 3) || temperature > (temp_max + 3)) {
// Temperatura fora do intervalo, usar a intensidade alta
OCR3B = PWM_LED_ALTA;
} else {
// Caso contrário, desliga o LED
OCR3B = PWM_LED_DES;
}
}
// Função para ler o valor digital do sensor DHT22
uint8_t dht_read(uint8_t dht, enum DHT_Type type, int tem_led)
{
uint8_t dht_data[5] = {0};
// dht_data[0] = Parte inteira da umidade
// dht_data[1] = Parte decimal da umidade
// dht_data[2] = Parte inteira da temperatura
// dht_data[3] = Parte decimal da temperatura
// dht_data[4] = Verificador, soma dos dos outros elementos
// Enviar sinal de inicialização para o sensor
// Configurando o pino como saída
DDRH |= (1 << dht);
// Enviando sinal de inicialização
// OLÁ
PORTH &= ~(1 << dht);
// Delays específicos para tipo DHT22 e DHT11
switch(_type)
{
case DHT22:
_delay_us(1000);
break;
case DHT11:
_delay_ms(18);
break;
}
// Iniciando comunicação com o sensor
// OLÁ
PORTH |= (1 << dht);
// Delays específicos para tipo DHT22 e DHT11
switch(_type)
{
case DHT22:
_delay_us(40);
break;
case DHT11:
_delay_us(20);
break;
}
// Depois de enviando sinal para o sensor, está na hora de receber
// Configurando o pino como entrada
// OI
DDRH &= ~(1 << dht);
// Conferindo se há algum erro
while (PINH & (1 << dht)); // PORTH em nível lógico 1, sensor não está pronto para responder
while (!(PINH & (1 << dht))); // PORTH em nível lógico 0, sensor está pronto para responder
while (PINH & (1 << dht)); // Garante que o sensor DHT está realmente pronto para responder
// Lendo os dados do sensor
dht_data[5] = {0};
// Percorrendo os cinco elementos do vetor dht_data
for (uint8_t i = 0; i < 5; ++i)
{
uint8_t bytee = 0; // Reiniciar do byte zero em cada elemento
// Lendo cada um dos 8 bits do elemento
for (uint8_t j = 0; j < 8; ++j)
{
// Espera até que o pino esteja em um nível alto (aguardando pulso de dados)
while (!(PINH & (1 << dht)));
_delay_us(30); // Espera para determinar se o bit é 0 ou 1
if (PINH & (1 << dht)) bytee |= (1 << (7 - j)); // Bit é 1
// Espera até que o pino esteja em um nível baixo (final do pulso)
while (PINH & (1 << dht));
}
// Armazenando bytes em cada elemento do vetor
dht_data[i] = bytee;
}
// Verificando a soma de verificação
if ((uint8_t)(dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) != dht_data[4])
{
return 6; // Erro de checksum
}
// Calculando temperatura e umidade
switch (type)
{
case DHT22:
// Unir parte inteira e decimal
// Está garantindo que retorne uma temperatura positiva, pois essa máscara (0x7F) garante que o bit mais significativo (sinal) seja forçado a 0
// Garantindo uma interpretação e calculo certo
temperature = ((dht_data[2] & 0x7F) << 8 | dht_data[3]) / 10.0;
// humidity = ((uint16_t)dht_data[0] << 8) | dht_data[1];
// Atribuir aos decimais
temperature_decimal = temperature / 10;
// humidity_decimal = humidity / 10;
// Corrigir sinal da temperatura
if (dht_data[2] & 0x80) temperature_decimal *= -1;
break;
case DHT11:
// Unir parte inteira e decimal
temperature = ((uint16_t)dht_data[2]);
// humidity = (uint16_t)dht_data[0] + dht_data[1] * 0.1;
// Atribuir aos decimais
temperature_decimal = ((float)temperature);
// humidity_decimal = ((float)humidity);
// Corrigir sinal da temperatura
if (dht_data[3] & 0x80)
{
temperature = -1-temperature;
}
temperature += (dht_data[3] & 0x0f) * 0.1;
break;
}
// Serial.println(temperature);
char tempString[16]={0};
dtostrf(temperature, 6, 2, tempString);
// Exibindo os valores lidos
lcd_cmd(0x01, 0); // Limpa o display
_delay_ms(2);
lcd_cmd(0x80, 0); // Posiciona o cursor na primeira linha
lcd_write("Temp:");
// lcd_cmd(0x86, 0); // Posiciona o cursor após "Temp: "
// char buffer[8];
lcd_write(tempString);
lcd_write(" C");
if(tem_led == 1)
{
lcd_cmd(0xC0, 0); // Posiciona o cursor na segunda linha
lcd_write("LED: ON"); // Escreve LED ON
led_control(temperature); // Atualiza LED (recursivamente, ao ser chamada no while da main)
}else
{
lcd_cmd(0xC0, 0); // Posiciona o cursor na segunda linha
lcd_write("LED: OFF"); // Escreve LED ON
OCR3B = 0; // Desliga LED
}
// Escreve a temperatura atual na última posição do vetor
med[9] = temperature;
// Transforma o valor da média em string inteira
dtostrf(average, 2, 0, tempString);
// Atualiza o LCD com a média
lcd_cmd(0xC9, 0); // Posiciona o cursor na segunda linha, coluna 9
lcd_write("M:"); // Nome média "M"
lcd_cmd(0xCC, 0); // Posiciona na primeira posição da segunda linha do display
lcd_write(tempString); // Escreve a string da média
lcd_write(" C"); // Em Celsius
return 0; // Sucesso
}