#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "unistd.h"

/*    Chương trình thiết lập cấu hình hoạt động của I2C-PCF8574 
      dựa trên bảng Pin-mapping và tài liệu espressif.
      I2C hỗ trợ 3 tần số: 
            100kHz (standard mode), 
            400kHz (fast mode)
            3.4MHz (high-speed mode)  */
#define I2C_MASTER_FREQ_HZ 100000
#define I2C_MASTER_SDA_IO GPIO_NUM_21
#define I2C_MASTER_SCL_IO GPIO_NUM_22
#define I2C_MASTER_NUM I2C_NUM_1
void i2c_master_init(void)
{
  printf("I2C config ...\n");
  i2c_config_t conf = {
      .mode = I2C_MODE_MASTER,
      .sda_io_num = GPIO_NUM_21,
      .scl_io_num = GPIO_NUM_22,
      .sda_pullup_en = GPIO_PULLUP_ENABLE,
      .scl_pullup_en = GPIO_PULLUP_ENABLE,
};
// Wokwi không hỗ trợ .A.B trong khai báo cấu trúc, Đây là giải pháp thay thế.
  conf.master.clk_speed = 100000;
/*   I2C hoàn thành sau khi thực hiện i2c_param_config() và i2c_driver_install.
     Tham khảo espressif     */
  i2c_param_config(I2C_MASTER_NUM, &conf);
  i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
  printf("I2C Done.\n");
}

/*   -------------------------------------------------------
     Đoạn code truyền một COMMAND 8-Bit qua giao diện 4-Bit.
     -------------------------------------------------------  */
#define SLAVE_ADDRESS_LCD 0x27 // A2 A1 A0 = 1 1 1
esp_err_t err;			// tạo biến ghi nhận lỗi để kiểm tra
void lcd_send_cmd (char cmd)
{
  printf("LCD cmd: %02hhX | ",cmd&0xff);
  esp_err_t err;
  char data_u, data_l;
  uint8_t data_t[4];
  data_u = (cmd&0xf0);	   // Lấy 4 Bit cao D7-D4
data_l = ((cmd<<4)&0xf0); // Lấy 4 Bit thấp D3-D0 và dịch chuyển lên D7-D4
/*   Chèn các bit điều khiển vô Sub-Byte, tạo Byte mới. 
   Tạo xung cạnh xuống E để LCD đọc D7-D4. Do đó,
   sử dụng data_t[0] data_t[1] chỉ để truyền Sub-Byte data _t 
   Tương tự cho Sub-Byte data_l 
   RS = 0 để báo cho slave đây là COMMAND   */
  data_t[0] = data_u|0x0C;  // OR  0000-1100 | => E=1, RS=0
  data_t[1] = data_u|0x08;  // OR  0000-1000 | => E=0, RS=0
  data_t[2] = data_l|0x0C;  // OR  0000-1100 | => E=1, RS=0
  data_t[3] = data_l|0x08;  // OR  0000-1000 | => E=0, RS=0
/*   i2c_master_write_to_device() dùng để gửi dữ liệu từ Master sang Slave.
   Thông số theo quy định trong tài liệu espressif    */
  err = i2c_master_write_to_device (I2C_MASTER_NUM, SLAVE_ADDRESS_LCD, data_t, 4, 1000);
  printf(" | Err No. = %d | ", err);
  printf("Done.\n");
}

/*   ----------------------------------------------------
     Đoạn code truyền một DATA 8-Bit qua giao diện 4-Bit.
     ----------------------------------------------------  */
void lcd_send_data(char data){
  printf("LCD dat: %c | ",data);
  char data_u, data_l;
  uint8_t data_t[4];
  data_u = (data&0xf0);
data_l = ((data<<4)&0xf0);
/*   RS = 0 để báo cho slave đây là DATA   */
  data_t[0] = data_u|0x0D;  // OR  0000-1101 | => en=1, rs=1
  data_t[1] = data_u|0x09;  // OR  0000-1001 | => en=0, rs=1
  data_t[2] = data_l|0x0D;  // OR  0000-1101 | => en=1, rs=1
  data_t[3] = data_l|0x09;  // OR  0000-1001 | => en=0, rs=1
  err = i2c_master_write_to_device(I2C_MASTER_NUM, SLAVE_ADDRESS_LCD, data_t, 4, 1000);
  printf("Err No. = %d | ", err);
  printf("Done.\n");
}

/*    Chương trình khởi tạo LCD:
            - Cài đặt chế độ hoạt động 4-Bit Interface
            - Cài đặt LCD: số dòng, cột; hiển thị con trỏ; 
              dịch chuyển con trỏ  */
void lcd_init (void)
{
  printf("LCD config...\n");
  // 4 bit initialisation
  usleep(50000);  // wait for >40ms
  lcd_send_cmd (0x33); // [NEED] select 8-bit interface
  usleep(4500);  // wait for >4.1ms
  lcd_send_cmd (0x33); // [NEED] select 8-bit interface again
  usleep(200);  // wait for >100us
  lcd_send_cmd (0x33);
  lcd_send_cmd (0x32); // [NEED] select 4-bit interface

  lcd_send_cmd (0x28); // [NEED] set 2 lines and 5x7(8?) dots per character
  lcd_send_cmd (0x0C); // [NEED] display on, cursor off, corsor blinking off - 0x0C or 0x0F
  lcd_send_cmd (0x06); // [No-NEED] move cursor right when writing, no scroll
  lcd_send_cmd (0x80); // [No-NEED] set cursor to home position (row 1, column 1)
  printf("LCD Done.\n");
}

/*    Chương trình thiết lập tọa độ corsor tại DDRAM    */
void lcd_put_cur(int row, int col)
{
    switch (row)
    {
/*  col luôn bé hơn 16 (16 cột LCD), phép |= 
    sẽ đảm bảo vị trí đúng bảng địa chỉ DDRAM   */
        case 0:
            col |= 0x80;	// Hàng 1 của bộ nhớ
            break;
        case 1:
            col |= 0xC0; // Hàng 2 của bộ nhớ
            break;
    }
    lcd_send_cmd (col);
}

/*    Chương trình nhận địa chỉ (pointer) của dữ liệu cần in 
      và xuất ra LCD    */
void lcd_send_string (char *str)
{
  while (*str) lcd_send_data (*str++);
}

/*    Có thể clear màn hình thông qua Instrution 0x01  (theo Datasheet 
      HD44780 trang 24 lệnh clear display)  */
void lcd_clear (void)
{
  lcd_send_cmd (0x01);
  usleep(5000);
}

/*    Wokwi yêu cầu extern “C” (thực thi C trên nền Cpp)
      Nếu dùng Eclipse IDE, cần xóa extern “C”  */
extern "C" void app_main(void)
{
    i2c_master_init();		// Thiết lập hoạt động I2C đúng với phần cứng
    lcd_init();			// Thiết lập hoạt động LCD
    lcd_clear();			// Clear màn hình

    lcd_put_cur(0, 1);		// Đặt con trỏ ở 
    lcd_send_string("Hello World!");
    

    lcd_put_cur(1, 3);
    lcd_send_string("from ESP32");
}