#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
// I2C Address of PCF8574 (change to 0x3F if needed)
#define I2C_ADDR 0x27
// LCD Control bits via PCF8574
#define LCD_BACKLIGHT 0x08
#define ENABLE 0x04
#define RW 0x02
#define RS 0x01
// Initialize I2C
void I2C_init()
{
TWSR = 0x00; // No prescaler
TWBR = ((F_CPU / 100000UL) - 16) / 2; // SCL freq = 100kHz
}
// Start condition
void I2C_start()
{
TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
while (!(TWCR & (1<<TWINT)));
}
// Stop condition
void I2C_stop()
{
TWCR = (1<<TWSTO)|(1<<TWEN)|(1<<TWINT);
while (TWCR & (1<<TWSTO));
}
// Send byte
void I2C_write(uint8_t data)
{
TWDR = data;
TWCR = (1<<TWEN)|(1<<TWINT);
while (!(TWCR & (1<<TWINT)));
}
// Send address
void I2C_send_address(uint8_t addr)
{
I2C_start();
I2C_write(addr << 1); // Write mode
}
// Write nibble to LCD
void LCD_write_nibble(uint8_t nibble, uint8_t mode)
{
uint8_t data = (nibble & 0xF0) | LCD_BACKLIGHT;
if (mode) data |= RS; // RS=1 for data
else data &= ~RS; // RS=0 for command
// Pulse Enable
I2C_send_address(I2C_ADDR);
I2C_write(data | ENABLE);
_delay_us(1);
I2C_write(data & ~ENABLE);
I2C_stop();
}
// Send byte to LCD
void LCD_send_byte(uint8_t byte, uint8_t mode)
{
LCD_write_nibble(byte & 0xF0, mode); // Send high nibble
LCD_write_nibble((byte << 4) & 0xF0, mode); // Send low nibble
_delay_us(50);
}
// LCD Commands
void LCD_command(uint8_t cmd)
{
LCD_send_byte(cmd, 0);
}
void LCD_data(uint8_t data)
{
LCD_send_byte(data, 1);
}
void LCD_init()
{
_delay_ms(50);
LCD_write_nibble(0x30, 0); // Wake up
_delay_ms(5);
LCD_write_nibble(0x30, 0); // Wake up
_delay_us(150);
LCD_write_nibble(0x30, 0); // Wake up
LCD_write_nibble(0x20, 0); // 4-bit mode
LCD_command(0x28); // 4-bit, 2-line
LCD_command(0x0C); // Display ON, Cursor OFF
LCD_command(0x06); // Entry mode
LCD_command(0x01); // Clear display
_delay_ms(2);
}
void LCD_setCursor(uint8_t row, uint8_t col) {
uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
LCD_command(0x80 | (col + row_offsets[row]));
}
void LCD_print(const char* str) {
while (*str) {
LCD_data(*str++);
}
}
void setup() {
I2C_init();
LCD_init();
LCD_setCursor(0, 0);
LCD_print("Hello I2C LCD!");
}
void loop() {
// Do nothing
}