#define F_CPU 16000000UL // Define the CPU frequency as 16 MHz #include #include // Define LCD pins #define LCD_E PIN0_bm // Enable (PC0) #define LCD_RS PIN1_bm // Register Select (PC1) #define LCD_D4 PIN3_bm // Data Bit 4 (PC3) #define LCD_D5 PIN2_bm // Data Bit 5 (PC2) #define LCD_D6 PIN5_bm // Data Bit 6 (PC5) #define LCD_D7 PIN4_bm // Data Bit 7 (PC4) // Helper macros #define LCD_E_HIGH() (PORTC.OUTSET = LCD_E) #define LCD_E_LOW() (PORTC.OUTCLR = LCD_E) #define LCD_RS_HIGH() (PORTC.OUTSET = LCD_RS) #define LCD_RS_LOW() (PORTC.OUTCLR = LCD_RS) // Function Prototypes void lcd_send_nibble(uint8_t nibble); void lcd_send_byte(uint8_t data, uint8_t is_command); void lcd_init(); void lcd_print(const char *str); void lcd_set_cursor(uint8_t row, uint8_t col); // Send a nibble (4 bits) to the LCD void lcd_send_nibble(uint8_t nibble) { PORTC.OUTCLR = LCD_D4 | LCD_D5 | LCD_D6 | LCD_D7; // Clear D4-D7 // Set data lines based on nibble if (nibble & 0x01) PORTC.OUTSET = LCD_D4; if (nibble & 0x02) PORTC.OUTSET = LCD_D5; if (nibble & 0x04) PORTC.OUTSET = LCD_D6; if (nibble & 0x08) PORTC.OUTSET = LCD_D7; // Pulse E pin LCD_E_HIGH(); // Set E high _delay_us(1); // Pulse duration LCD_E_LOW(); // Set E low _delay_us(100); // Command latch time } // Send a byte (8 bits) to the LCD void lcd_send_byte(uint8_t data, uint8_t is_command) { if (is_command) { LCD_RS_LOW(); // Command mode } else { LCD_RS_HIGH(); // Data mode } lcd_send_nibble(data >> 4); // Send high nibble lcd_send_nibble(data & 0x0F); // Send low nibble _delay_us(50); // Command execution time } void lcd_init() { // Step 1: Initialize LCD pins PORTC.OUTCLR = LCD_E | LCD_RS | LCD_D4 | LCD_D5 | LCD_D6 | LCD_D7; // Clear pins PORTC.DIRSET = LCD_E | LCD_RS | LCD_D4 | LCD_D5 | LCD_D6 | LCD_D7; // Set as outputs LCD_E_LOW(); LCD_RS_LOW(); _delay_ms(50); // Wait for VDD stabilization (>40ms) // Step 2: Force 8-bit mode initialization (special sequence for 4-bit mode) lcd_send_nibble(0x03); // Upper nibble of 0x30 _delay_ms(5); lcd_send_nibble(0x03); // Repeat initialization sequence _delay_us(200); lcd_send_nibble(0x03); // Repeat initialization sequence _delay_us(200); lcd_send_nibble(0x02); // Switch to 4-bit mode _delay_us(200); // Step 3: Function Set (4-bit, 2-line, instruction table 1) lcd_send_nibble(0x02); // Upper nibble of 0x28 lcd_send_nibble(0x08); // Lower nibble of 0x28 _delay_us(200); // Step 8: Display ON/OFF Control lcd_send_nibble(0x00); // Upper nibble of 0x0C lcd_send_nibble(0x0C); // Lower nibble of 0x0C _delay_us(200); // Step 9: Clear Display lcd_send_nibble(0x00); // Upper nibble of 0x01 lcd_send_nibble(0x01); // Lower nibble of 0x01 _delay_ms(2); // Step 10: Entry Mode Set lcd_send_nibble(0x00); // Upper nibble of 0x06 lcd_send_nibble(0x06); // Lower nibble of 0x06 _delay_us(200); } // Print a string to the LCD void lcd_print(const char *str) { while (*str) { lcd_send_byte(*str++, 0); // Send characters in data mode } } // Set cursor to a specific position void lcd_set_cursor(uint8_t row, uint8_t col) { uint8_t address = (row == 0) ? col : (0x40 + col); lcd_send_byte(0x80 | address, 1); } int main() { lcd_init(); // Initialize the LCD // Display a test message lcd_set_cursor(0, 0); // First row, first column lcd_print("Testing LCD"); lcd_set_cursor(1, 0); // Second row, first column lcd_print("1234567890"); while (1); // Infinite loop to hold the display }