@milliways answer certainly is for more experienced backgrounds.
But below has been my brute force learning the theory Introduction to I2C bus and the rest with reading the default I2C standard I2C standard - maybe not up to date to finally succeeding to display in my LCD to say "Hello World".
ESPECIALLY learning the importance of the data sheet of the target device LCD (TD) which I was trying to instruct. Which was in my case the LCD Module Data Sheet.
First On learning the concept of how I2C communication works - on sending data via bytes and to instantiate variables down the line using the built in <linux/12c-dev.h> and <sys/ioctl.h> - intuitively, the same concept of trying to open a file and writing to it...
#include <stdio.h> // Standard I/O functions (e.g., printf, perror)
#include <fcntl.h> // File control options used by open() for device file access
#include <unistd.h> // Unix standard functions (e.g., close, sleep, usleep)
#include <linux/i2c-dev.h> // Definitions and macros for I²C communication (e.g., I2C_SLAVE)
#include <sys/ioctl.h> // For controlling device parameters via ioctl() (e.g., setting device address)
#include <stdlib.h> // Standard library functions (e.g., strtol for conversions)
#define I2C_PATH "/dev/i2c-1" // Path to the I²C device
#define LCD_ADDR 0x27 // I²C address for our LCD
Second the very important role of data-sheet of the TD that I had.
For example, TD needed enough time between receiving data, therefore I had to use usleep() frequently. Furthermore, TD needed to know whether it would communicate in 8 bit or 4 bit (such as when you might have all 8/10 pins connected to RPI5 or only the "actual I2C 4 pins interface; 8bit writes or 2 sets of 4 bit rights respectively). I could tell from the I2C I/O repeater chip, I could send only 4 bits at a time.
But what was most important was the prerequisites before TD can display some characters
#define LCD_CLEAR_DISPLAY 0x01
#define LCD_RETURN_HOME 0x02
#define LCD_ENTRY_MODE_SET 0x06
#define LCD_DISPLAY_ON_CURSOR_OFF_BLINK_OFF 0x0C
#define LCD_FUNCTION_SET_4BIT 0x28
#define LCD_SET_DDRAM_ADDR_LINE_1 0x80
#define LCD_SET_DDRAM_ADDR_LINE_2 0xC0
This then naturally went into a simple void function to initialise all as a whole in main:
// Send a command (RS = 0) to the LCD
void lcd_command(int file, unsigned char cmd) {
write_4bit(file, cmd, 0x00);
usleep(2000);
}
// Send data (RS = 1) to the LCD
void lcd_data(int file, unsigned char data) {
write_4bit(file, data, 0x01);
usleep(200);
}
// Initialize the LCD in 4-bit mode following the datasheet sequence
void lcd_init(int file) {
usleep(50000); // Wait >40ms for power stabilization
lcd_command(file, 0x33); // Initialization sequence part 1 (8-bit temporarily)
lcd_command(file, 0x32); // Initialization sequence part 2: switch to 4-bit mode
lcd_command(file, LCD_FUNCTION_SET_4BIT); // Configure for 4-bit, 2 line, 5x8 font
lcd_command(file, LCD_DISPLAY_ON_CURSOR_OFF_BLINK_OFF); // Turn display on
lcd_command(file, LCD_CLEAR_DISPLAY); // Clear the display
usleep(2000);
lcd_command(file, LCD_ENTRY_MODE_SET); // Set entry mode (increment cursor)
lcd_command(file, LCD_RETURN_HOME); // Return cursor to home position
}
I had to configure the RPI5 and the Device (connected together) to start "transmission" in a specific way (synchronise writing and reading data ensuring I had given the device enough time in milliseconds before sending more commands to format my display) as above refers.
The main() therefore went on naturally as follows:
int main() {
int file = open(I2C_PATH, O_RDWR); //open the I2C device file with some debugging below
if (file < 0) {
perror("Failed to open I2C bus");
return -1;
}
// join the file device file with the address TD (CLI: i2cdetect -y 1). Second parameter "I2C_SLAVE" is a defauly macro from kernel to state we are about to set a "target device" less commonly referred to as a slave device.
if (ioctl(file, I2C_SLAVE, LCD_ADDR) < 0) {
perror("Failed to set I2C slave address");
close(file);
return -1;
}
lcd_init(file);
write_string_to_line(file, "Hello", LCD_SET_DDRAM_ADDR_LINE_1);
write_string_to_line(file, "World", LCD_SET_DDRAM_ADDR_LINE_2);
close(file);
return 0;
}
Finally thinking my code wasn't working when in fact, my LCD was connected to a 3.3v and the data sheet specified at least 4v, therefore it was difficult to see the shade of black text "Hello World" emitting from the LCD display.