#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#define F_CPU 16000000UL
#define LDR_PIN 0
#define DHT_PIN 6 // DHT22 sensörünün bağlı olduğu pin

// LCD ve I2C fonksiyonları için tanımlamalar
#define LCD_I2C_ADDRESS 0x27
#define LCD_BACKLIGHT 0x08
#define ENABLE 0x04
#define COMMAND_MODE 0x00
#define DATA_MODE 0x01

// I2C başlatma fonksiyonu
void I2C_Init() {
    TWSR = 0x00; // Set prescaler to 1
    TWBR = 0x47; // Set SCL frequency to 100 kHz
    TWCR = (1 << TWEN); // Enable TWI
}

void I2C_Start() {
    TWCR = (1 << TWSTA) | (1 << TWEN) | (1 << TWINT); // Send start condition
    while (!(TWCR & (1 << TWINT))); // Wait for TWINT flag set
}

void I2C_Stop() {
    TWCR = (1 << TWSTO) | (1 << TWEN) | (1 << TWINT); // Send stop condition
    while (TWCR & (1 << TWSTO)); // Wait for stop condition to be executed
}

void I2C_Write(uint8_t data) {
    TWDR = data; // Load data into TWDR register
    TWCR = (1 << TWEN) | (1 << TWINT); // Start transmission
    while (!(TWCR & (1 << TWINT))); // Wait for TWINT flag set
}

void I2C_Write_Byte(uint8_t data) {
    I2C_Start();
    I2C_Write(LCD_I2C_ADDRESS << 1);
    I2C_Write(data | LCD_BACKLIGHT);
    I2C_Stop();
}
// I2C başlatma fonksiyonunun geri kalanı buraya eklenebilir
void LCD_Write_Nibble(uint8_t nibble, uint8_t mode) {
    I2C_Write_Byte(nibble | mode | ENABLE);
    I2C_Write_Byte(nibble | mode);
}
// LCD'ye bir byte gönderme fonksiyonu
void LCD_Write_Byte(uint8_t data, uint8_t mode) {
    LCD_Write_Nibble(data & 0xF0, mode);
    LCD_Write_Nibble((data << 4) & 0xF0, mode);
}
void LCD_Command(uint8_t cmnd) {
    LCD_Write_Byte(cmnd, COMMAND_MODE);
    _delay_ms(2);
}

// LCD'ye bir karakter gönderme fonksiyonu
void LCD_Char(uint8_t data) {
    LCD_Write_Byte(data, DATA_MODE);
    _delay_ms(2);
}

// LCD'ye bir string gönderme fonksiyonu
void LCD_String(char *str) {
    while (*str) {
        LCD_Char(*str++);
    }
}
void LCD_Init() {
    I2C_Init();
    _delay_ms(50);
    LCD_Write_Nibble(0x30, COMMAND_MODE);
    _delay_ms(5);
    LCD_Write_Nibble(0x30, COMMAND_MODE);
    _delay_ms(1);
    LCD_Write_Nibble(0x30, COMMAND_MODE);
    _delay_ms(1);
    LCD_Write_Nibble(0x20, COMMAND_MODE); // Set to 4-bit mode

    LCD_Command(0x28); // 2 line, 5x7 matrix
    LCD_Command(0x0C); // Display on, cursor off
    LCD_Command(0x06); // Increment cursor
    LCD_Command(0x01); // Clear display
    _delay_ms(2);
}
void LCD_SetCursor(uint8_t row, uint8_t col) {
    uint8_t pos = (row == 0) ? (0x80 + col) : (0xC0 + col);
    LCD_Command(pos);
}


// LCD ekranını temizleme fonksiyonu
void LCD_Clear() {
    LCD_Write_Byte(0x01, COMMAND_MODE); // LCD ekranını temizle
    _delay_ms(2); // Bekleme süresi
}

// LDR değerini LCD'ye yazdırma fonksiyonu
void Display_LDR_Value(uint16_t ldr_value) {
    char buffer[10];
    LCD_Clear(); // Ekranı temizle
    sprintf(buffer, "LDR: %d", ldr_value); // LDR değerini stringe çevir
    LCD_String(buffer); // LDR değerini LCD'ye yaz
}

void Display_DHT22_Values(int temperature, int humidity) {
    char buffer[20];
    LCD_Clear(); // Ekranı temizle
    sprintf(buffer, "Temp: %d C", temperature); // Sıcaklık değerini stringe çevir
    LCD_String(buffer); // Sıcaklık değerini LCD'ye yaz
    LCD_SetCursor(1, 0); // İkinci satıra geç
    sprintf(buffer, "Humidity: %d%%", humidity); // Nem değerini stringe çevir
    LCD_String(buffer); // Nem değerini LCD'ye yaz
}
uint8_t DHT22_Read(float* temperature, float* humidity) {
    uint8_t bits[5] = {0};
    uint8_t i, j = 0;

    // Pin konfigürasyonu
    DDRD |= (1 << DHT_PIN); // DHT_PIN çıkış olarak ayarla
    PORTD &= ~(1 << DHT_PIN); // DHT_PIN'i düşük yap
    _delay_ms(20); // 20 ms bekle
    PORTD |= (1 << DHT_PIN); // DHT_PIN'i yüksek yap
    _delay_us(40); // 40 us bekle
    DDRD &= ~(1 << DHT_PIN); // DHT_PIN giriş yap
    _delay_us(10); // 10 us bekle

    // Sensörden 40 bit veri okuma
    for (i = 0; i < 5; ++i) {
        for (j = 0; j < 8; ++j) {
            uint32_t timeout = 10000; // Zaman aşımı kontrolü için
            while (!(PIND & (1 << DHT_PIN)) && timeout--); // DHT_PIN'in yüksek olmasını bekle
            if (timeout == 0) {
                return 0; // Zaman aşımında hata dön
            }
            _delay_us(30); // 30 us bekle
            if (PIND & (1 << DHT_PIN)) bits[i] |= (1 << (7 - j)); // Eğer DHT_PIN yüksek ise
            timeout = 10000;
            while (PIND & (1 << DHT_PIN) && timeout--); // DHT_PIN'in düşük olmasını bekle
            if (timeout == 0) {
                return 0; // Zaman aşımında hata dön
            }
        }
    }

    // Veri doğrulama
     if ((uint8_t)(bits[0] + bits[1] + bits[2] + bits[3]) != bits[4]) {
        return 3;
    }

    *humidity = ((bits[0] << 8) + bits[1]) * 0.1;
    *temperature = (((bits[2] & 0x7F) << 8) + bits[3]) * 0.1;
    if (bits[2] & 0x80) {
        *temperature = -(*temperature);
    }
} 

// I2C'yi başlatma fonksiyonu
void setup() {
    LCD_Init(); // Initialize LCD
    LCD_SetCursor(0, 0); // Set cursor to first line, first position
}

// Ana döngü
void loop() {
    uint16_t ldr_value = adc_read(LDR_PIN); // LDR değerini oku
    Display_LDR_Value(ldr_value);
    float temperature = 0.0, humidity = 0.0;
   
    if (DHT22_Read(&temperature, &humidity)) {
      
        Display_DHT22_Values(temperature, humidity);
        
    } else {
        LCD_Clear();
        LCD_String("Error reading DHT22");
    }
    
    _delay_ms(1000); // 1 saniye bekle
    
}

// ADC başlatma fonksiyonu
void adc_init(void) {
    ADMUX = (1 << REFS0); // Referans voltajını AVCC olarak ayarla
    ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // ADC'yi etkinleştir, öntanımlı bölücü 128
}

// ADC okuma fonksiyonu
uint16_t adc_read(uint8_t channel) {
    ADMUX = (ADMUX & 0xF0) | (channel & 0x0F); // ADC kanalını seç
    ADCSRA |= (1 << ADSC); // Dönüşümü başlat
    while (ADCSRA & (1 << ADSC)); // Dönüşümün tamamlanmasını bekle
    return ADC; // ADC değerini döndür
}