EmbeddedRelated.com
Forums

Sending Data From TI TM4C123GH6PM to an HD4478 LCD with a PCF8574T I2C Backpack

Started by Elliott_Embedded 3 years ago6 replieslatest reply 3 years ago295 views

Hello everyone, I am trying to write a program that will enable me to send data from my TI microcontroller to the very common HD4478 LCD. Rather than utilizing a parallel pin setup, I thought it would be a good idea to try and use a serial setup, so the LCD has a PCF8574T I2C I/O expander backpack. I am fairly new to writing embedded programs, and this is my first time using any real serial wiring communication protocols (I2C/SPI) so I'm struggling a bit to get this to work. Before I explain my confusion, here are the datasheets for the 3 respective components:


Microcontroller Data Sheet: https://www.ti.com/lit/ds/symlink/tm4c123gh6pm.pdf

LCD data sheet: https://circuitdigest.com/sites/default/files/HD44...

I/O Expander Data Sheet: https://www.nxp.com/docs/en/data-sheet/PCF8574_PCF...


My main problem is, I have no real idea what I'm doing wrong, but I am assuming it's something with the way I am initializing the LCD to 4-bit mode. I am a bit confused by this initialization walkthrough on the LCD data sheet:


init_circuit_40561.png


And then This latter explanation that further explains how to initialize it to 4 bit mode, which is what I'll be using with my I/O expander:


screenshot from 2021-10-16 00-14-15_6066


The way I transfer data over the data line is the 4-bit mode, so I am sending the upper nibble. and then lower nibble. This goes for both data bytes and command bytes. Below is my code. Currently flashing it and running simply has no effect on the LCD. I know that I am operating at the correct slave address since I do not get any flags present after the initial slave address transmit that would indicate any error. However, all of my commands in I2C_LCD_Enable and the latter command in main() that attempts to send a singular character to the LCD have no effect as well.


#include "TM4C123.h"   // Device header
#include "RTE_Components.h"             // Component selection

//data pin should be open drain in i2c modules


#define DATA 1

#define COMMAND 0

//I can think of each Pin connected to command/control as a singular bit


void GPIOE_enable(void);
void I2C_enable(void);

void I2C_LCD_enable(void);
void I2C_transfer_byte(char byte,int mode);

void delay_50_ms(void);

uint32_t address_transfer_value;
uint32_t data_value;

#define E 0x04 //bit 2

int main(){
    GPIOE_enable();
    I2C_enable();
    I2C_LCD_enable();
    I2C_transfer_byte(0x01,COMMAND);
    I2C_transfer_byte(0x80,COMMAND); //set cursor to first row
    delay_50_ms();
    I2C_transfer_byte('a',DATA);
}

//port E, pins pe4 and pe5, have the alternative function as acting the clock/data lines for I2C module 2
void GPIOE_enable(void){
    SYSCTL->RCGCGPIO |= 0x10; //enable port E
    GPIOE->DIR |= 0x10 | 0x20;
    GPIOE->DEN |= 0x10 | 0x20; //enable pins pe4 and pe5
    GPIOE->AFSEL = 0x10 | 0x20; //enable pins Pe4 and Pe5 for their alternate function
    GPIOE->ODR |= 0x20; //pe5 is data pin, must be set to open drain
    GPIOE->PCTL |= (3 << 16) | (3 << 20);
}

void I2C_enable(void){
    SYSCTL->RCGCI2C |= 0x04;
    I2C2->MCR |= 0x10; //initialize I2C master
    GPIOE->PUR |= 0x10 | 0x20; //I pulled up the Clock and Data Lines because they were low: Not pulling them up won't allow them to transfer from their high to low states when transmission begins and the I2C->MCS & 0x01 condition hangs forever
    I2C2->MTPR = 0x09; //see data sheet: This initializes SCL speed to 100k kbps
    I2C2->MSA = (0x27 << 1); //see data sheet: This sets slave address and sets mode to TRANSMIT
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
}

//HD775 data sheet explains the initialization process on pages 24 and 42
void I2C_LCD_enable(void){
    //not sure how to initialize quite yet...
    /*I2C2->MDR = 0x28; //this initializes 4-bit mode. This command, AND THIS COMMAND ONLY, takes only one write since it's still in 8-bit mode
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
    delay_50_ms();
    I2C2->MDR = (1 << E); //set ENABLE to high
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
    delay_50_ms();
    I2C2->MDR = ~(1<<E); //set ENABLE to low
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
    delay_50_ms();*/
    I2C_transfer_byte(0x28,COMMAND);
    I2C_transfer_byte(0x06,COMMAND); //Move cursor right
    I2C_transfer_byte(0x01,COMMAND); //clear screen
    I2C_transfer_byte(0x0F,COMMAND); //turn display on
}
    
    
    

//the upper 4 bits are the data pins : D3, D4, D5, D6, D7 (bits 0-3)
//the lower 3 bits are: RS, RW, E
//to send a command, we should set RS to 0 to select "Command Mode" on LCD
//if mode is 0, or COMMAND, do a logical OR with 0, which will set RS, bit 0, to 0
//if mode is 1, or DATA, do a logical or with 1, so RS, bit 0, is set to 1
//we also need to pulse E, or enable to make sure any of these data/commands actually are executed
//The E pin corresponds to bit 2, so I'll send each data byte with first E enabled, and then E set to to low, to pulse Enable
//send upper nibble, pulse enable, send lower nibble, pulse enable
void I2C_transfer_byte(char byte,int mode){
            
    char byte_shifted;
    char byte_upper_nibble;
    char byte_lower_nibble;
    byte_shifted = byte << 4;
    byte_upper_nibble = byte & 0xF0;        
    I2C2->MDR = (mode | (I2C2->MDR & 0xF0)| byte_upper_nibble) | E; //set command to be most significant bit, and enable E
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01){
        data_value = I2C2->MBMON;
    }
    delay_50_ms();
    I2C2->MDR = (mode | (I2C2->MDR & 0xF0)| byte_upper_nibble) & ~E; //set command to be most significant bit, and disable E (pulsing E enables the command/data)
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
    delay_50_ms();
    byte_lower_nibble = byte_shifted & 0xF0;
    I2C2->MDR = (mode | (I2C2->MDR & 0x0F) | byte_lower_nibble) | E;
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
    delay_50_ms();
    I2C2->MDR = (mode | (I2C2->MDR & 0x0F) | byte_lower_nibble) & ~E;
    I2C2->MCS |= 0x07;
    while (I2C2->MCS & 0x01);
    delay_50_ms();
}



//clock frequency is 1,000,000 cycles/second
void delay_50_ms(void){
    SYSCTL->RCGCTIMER |= 0x01;
    TIMER0->CTL = 0;
    TIMER0->CFG |= 0x04;
    TIMER0->TAMR |= 0x01 | 0x10; //single shot mode, enable interrupt
    TIMER0->TAILR = 20000; //1,000,000 / 20,0000 = 50
    TIMER0->CTL = 0x01;
    while ((TIMER0->RIS & 0x01) == 0);
}



[ - ]
Reply by DilbertoOctober 18, 2021

Hi, Elliott_Embedded !

I don't use I2C for the display, but what works for me, in the LCD initialization, is :

  1. Delay 20 ms approximately
  2. Set size to 8 bits
  3. Delay 7 ms approximately
  4. Set size to 8 bits
  5. Delay 125 µs approximately
  6. Set size to 8 bits
  7. Delay 125 µs approximately
  8. Set size to 4 bits
  9. Wait LCD not busy( Handshake mode ) or wait enough time to LCD complete the task
  10. Activate LCD
  11. Wait LCD not busy( Handshake mode ) or wait enough time to LCD complete the task
  12. Clear LCD
  13. Wait LCD not busy( Handshake mode ) or wait enough time to LCD complete the task
  14. Turn ON/OFF Cursor, Turn ON Display and set Cursor Mode
  15. Wait LCD not busy( Handshake mode ) or wait enough time to LCD complete the task
  16. Return Cursor Home

Of course, this can be implemented sending the commands for the I2C driver instead of directly to the LCD.

Hope I've helped!



[ - ]
Reply by Elliott_EmbeddedOctober 18, 2021

Hi Dilberto, thanks for the response. I've realized since posting that my initialization was wrong, and am now attempting to initialize the LCD based off the Liquid Crystal I2C library for I2C. Here's the repo for that library:

GitHub - fdebrabander/Arduino-LiquidCrystal-I2C-library: Library for the LiquidCrystal LCD display connected to an Arduino board.


So now I have the following functions:


I2C_write_4_bits(uint8_t data): this method just calls expander write which logically or's the data with the backlight on flag and then sends said data to the slave with E set low. pulse_enable is then called which toggles E high/low so the LCD can lock in the data. This was done incorrectly before where I was sending the data with E high, and then data with E low, rather than sending the data with E low, and then high, and then low


expander_write(uint8_t data): this method just logically or's the data with the backlight on flag and sends it to the slave. the backlight should always be on of course so we can see what it is we're putting on the LCD!


pulse_enable(uint8_t data): this method calls expander_write first with a logical or'ing of E (set E to high with data after initial transmission where E is low) and then delays, and then and's the data with E set to low (set E to low, this creates the 'pulse' to lock in the command/data)


send(uint8_t data, int mode): method calls i2c_write_4_bits for the upper nibble and then the lower nibble with a logical or'ing of the mode flag (RS=0 is COMMAND, RS=1 is DATA)


command(uint8_t data): method calls send with the mode parameter set to 0

data(uint8_t data): method calls send with mode parameter set to 1

i2c_lcd_enable(void): method performs LCD initialization. Sets function mode to 8 bit-mode 3 times, then to 4-bit mode, then sets function set, display set, etc


However, even after these changes, I see no effect. I know my slave address is correct since I looped through the possible addresses for the I/O expander previously and 0x27 was the only address in which the ADACK flag was never set to 1, indicating it was the only address that was acknowledged by the slave. After that, the commands I send in initialization and any data I send after have zero effect, even attempting to turn off the backlight. Let me know if you see anything here that seems odd, I know I'm delaying enough between commands

void I2C_write_4_bits(uint8_t byte){            
 expander_write(byte);
 pulse_enable(byte);
}

void send(uint8_t data, int mode){
 char byte_shifted;
 char byte_upper_nibble;
 char byte_lower_nibble;
 byte_shifted = data << 4;
 byte_upper_nibble = data & 0xF0;
 byte_lower_nibble = byte_shifted & 0xF0;
 I2C_write_4_bits(byte_upper_nibble | mode);
 I2C_write_4_bits(byte_lower_nibble | mode);
}

void expander_write(uint8_t data){
I2C2->MDR = (data | LCD_BACKLIGHT); 
I2C2->MCS |= 0x07;
while (I2C2->MCS & 0x01);
}
void pulse_enable(uint8_t data){
    expander_write(data | E);
    delay_50_ms();
    expander_write(data & ~E);
    delay_50_ms();
}
void command(uint8_t data){
    send(data,COMMAND);
    delay_50_ms();
}
void data(uint8_t data){
    send(data,DATA);
    delay_50_ms();
}

void I2C_LCD_enable(void){
    I2C_write_4_bits(0x03 << 4);
    delay_50_ms();
    I2C_write_4_bits(0x03 << 4);
    delay_50_ms();
    I2C_write_4_bits(0x03 << 4);
    delay_50_ms();
    I2C_write_4_bits(0x02 << 4);
    command(LCD_FUNCTIONSET | LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS);
    command(LCD_DISPLAYCONTROL | LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF);
    command(LCD_CLEARDISPLAY);
    delay_50_ms();
    command(LCD_ENTRYMODESET | LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT);
    command(LCD_RETURNHOME);
    delay_50_ms();  
}

[ - ]
Reply by tcfkatOctober 18, 2021

Hello Elliott,


without digging to deep into your code ... your timing seems to be wrong. Afaik some of the HD44780 controller clones takes longer, especially if there are running on 3V3 instead 5V, due to their internal RC-oscillator.

In march this year I fixed a HD44780 routine for ESP32, see below. Two important things:

- respect the great delays during initialization

- respect the data setup and hold times. Data is latched into the HD44780 when E = high. So, write the appropriate data to the PCF8574 with E low, than the same data with E high, then the same again with E low.

This worked fine for me.


//***************************************************************************************************
// The backpack communicates with the I2C bus and converts the serial data to parallel for the      *
// 1602 board.  In the serial data, the 8 bits are assigned as follows:                             *
// Bit   Destination  Description                                                                   *
// ---   -----------  ------------------------------------                                          *
//  0    RS           H=data, L=command                                                             *
//  1    RW           H=read, L=write.  Only write is used.                                         *
//  2    E            Enable                                                                        *
//  3    BL           Backlight, H=on, L=off.  Always on.                                           *
//  4    D4           Data bit 4                                                                    *
//  5    D5           Data bit 5                                                                    *
//  6    D6           Data bit 6                                                                    *
//  7    D7           Data bit 7                                                                    *
//***************************************************************************************************
//
// Note that the display function are limited due to the minimal available space.
#include <stdio.h>
#include <string.h>
#include <driver/i2c.h>
//#define I2C_ADDRESS 0x3F   // For PCF8574A, often on 20x4 Displays
//#define I2C_ADDRESS 0x27   // For PCF8574, often on 16x2 Displays
#define ACKENA      true                                     // Enable ACK for I2C communication
#define DELAY_ENABLE_PULSE_SETTLE    50      // Command requires > 37us to settle
#define FLAG_BACKLIGHT_ON    0b00001000      // Bit 3, backlight enabled (disabled if clear)
#define FLAG_ENABLE          0b00000100      // Bit 2, Enable
#define FLAG_READ            0b00000010      // Bit 1, read busy flag (tcfkat 20210309)
#define FLAG_WRITE           0b00000000      // Bit 1, write (tcfkat 20210309)
#define FLAG_RS_DATA         0b00000001      // Bit 0, RS=data (command if clear)
#define FLAG_RS_COMMAND      0b00000000      // Command
#define COMMAND_CLEAR_DISPLAY               0x01 // takes longer, about 2ms! (tcfkat 20210309)
#define COMMAND_RETURN_HOME                 0x02 // takes longer, about 2ms! (tcfkat 20210309)
#define COMMAND_ENTRY_MODE_SET              0x04
#define COMMAND_DISPLAY_CONTROL             0x08
#define COMMAND_FUNCTION_SET                0x20
#define COMMAND_SET_DDRAM_ADDR              0x80
//
#define FLAG_DISPLAY_CONTROL_DISPLAY_ON     0x04
#define FLAG_DISPLAY_CONTROL_CURSOR_ON      0x02
//
#define FLAG_FUNCTION_SET_MODE_4BIT         0x00
#define FLAG_FUNCTION_SET_LINES_2           0x08
#define FLAG_FUNCTION_SET_DOTS_5X8          0x00
//
#define FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT 0x02
#define FLAG_ENTRY_MODE_SET_ENTRY_SHIFT_ON  0x01
#define DEBUG_BUFFER_SIZE 150
    void             print ( char c ) ;                     // Send 1 char
    void             reset() ;                              // Perform reset
    void             sclear() ;                             // Clear the screen
    void             shome() ;                              // Go to home position
    void             scursor ( uint8_t col, uint8_t row ) ; // Position the cursor
    void             scroll ( bool son ) ;                  // Set scroll on/off
    void             scommand ( uint8_t cmd ) ;
    void             strobe ( uint8_t cmd ) ;
    void             swrite ( uint8_t val, uint8_t rs ) ;
    void             write_cmd ( uint8_t val ) ;
    void             write_data ( uint8_t val ) ;
    uint8_t          bl  = FLAG_BACKLIGHT_ON ;              // Backlight in every command
    i2c_config_t     i2c_config ;                           // I2C configuration
    uint8_t          pcf8574address = 0;                    // PCF8574(A) I2C address found by bus scan
    uint8_t tft_sda_pin = 21;
    uint8_t tft_scl_pin = 22;
void dbgprint ( const char* format, ... )
{
  static char sbuf[DEBUG_BUFFER_SIZE] ;                // For debug lines
  va_list varArgs ;                                    // For variable number of params
  va_start ( varArgs, format ) ;                       // Prepare parameters
  vsnprintf ( sbuf, sizeof(sbuf), format, varArgs ) ;  // Format the message
  va_end ( varArgs ) ;                                 // End of using parameters
  Serial.println ( sbuf ) ;                          // and the info
  return;                                        // Return stored string
}
#if 0 // LATER ...
// Check busy flag of HD44780 as some commands may take very long,
// depending on command, Vdd, type of display and oscillator! (tcfkat 20210309)
void LCD1602::chkbusy(void)
{
  hnd = i2c_cmd_link_create() ;                           // Create a link
  esp_err_t err;                                          // changed (tcfkat 20210309)
  err  = i2c_master_start(hnd);
  err |= i2c_master_write_byte(hnd, (I2C_ADDRESS<<1)|I2C_MASTER_WRITE, ACKENA);
  err |= i2c_master_write_byte(hnd, 0xF0|FLAG_READ|bl, ACKENA);             // First raise read bit
  err |= i2c_master_write_byte(hnd, 0xF0|FLAG_READ|FLAG_ENABLE|bl, ACKENA); // Then raise enable bit
  err |= i2c_master_stop(hnd);
  err |= i2c_master_cmd_begin(I2C_NUM_0, hnd, 10/portTICK_PERIOD_MS);
  if (err) dbgprint ("LCD1602 communication error!");
  // Now, the HD44780 puts the 
  i2c_cmd_link_delete ( hnd ) ;                           // Link not needed anymore
}
#endif
#if 0 // No longer needed
void strobe ( uint8_t cmd )
{
  scommand ( cmd | FLAG_ENABLE ) ;                  // Send command with E high
  scommand ( cmd ) ;                                // Same command with E low
  delayMicroseconds ( DELAY_ENABLE_PULSE_SETTLE ) ; // Wait a short time
}
#endif
void swrite ( uint8_t val, uint8_t rs )          // General write, 8 bits data
{
  scommand ( ( val & 0xf0 ) | rs ) ;                        // Send 4 LSB bits
  scommand ( ( val << 4 ) | rs ) ;                          // Send 4 MSB bits
}
void write_data ( uint8_t val )
{
  swrite ( val, FLAG_RS_DATA ) ;                           // Send data (RS = HIGH)
  delayMicroseconds ( DELAY_ENABLE_PULSE_SETTLE ) ; // Wait a short time
}
void write_cmd ( uint8_t val )
{
  swrite ( val, FLAG_RS_COMMAND ) ;                 // Send command (RS = LOW)
  delayMicroseconds ( DELAY_ENABLE_PULSE_SETTLE ) ; // Wait a short time
}
// Scan I2C bus for PCF8574 or PCF8574A. First match wins. (tcfkat 20210310)
uint8_t i2c_pcf8574_scan (void)
{
  uint8_t i2cadd, start, end, tmp;
  i2c_cmd_handle_t hnd;
  esp_err_t err;
  // First scan for PCF8574 (address range 0x20...0x27),
  // then for PCF8574A (address range 0x38...0x3F)
  for (start = 0x20, end = 0x27; start <= 0x38; start += 0x18, end += 0x18)
    for (i2cadd = start; i2cadd <= end; i2cadd++){
      hnd = i2c_cmd_link_create(); // MUST be within the loop otherwhise FAIL!!!
      i2c_master_start(hnd);
      i2c_master_write_byte(hnd, (i2cadd<<1)|I2C_MASTER_READ, true); // Do a write for a read ...
      i2c_master_read_byte(hnd, &tmp, I2C_MASTER_NACK); // Dummy read
      i2c_master_stop(hnd);
      err = i2c_master_cmd_begin(I2C_NUM_0, hnd, 10/portTICK_PERIOD_MS);
      i2c_cmd_link_delete (hnd); // MUST be within the loop otherwhise FAIL!!!
      if (err == ESP_OK)
        return i2cadd;
    }
  // No PCF8574(A) found
  return 0;
}
void scommand ( uint8_t cmd )
{
  esp_err_t err;                                   // changed (tcfkat 20210309)
  i2c_cmd_handle_t hnd;
  uint8_t tmp  =  (cmd & ~FLAG_ENABLE)|bl,         // Signal E low
          tmp1 =   cmd |  FLAG_ENABLE |bl;         // Signal E high
  hnd = i2c_cmd_link_create() ;                    // Create a link
  i2c_master_start(hnd);
  i2c_master_write_byte(hnd, (pcf8574address<<1)|I2C_MASTER_WRITE, ACKENA);
  i2c_master_write_byte(hnd, tmp,  ACKENA);        // Write cmd with E low
  i2c_master_write_byte(hnd, tmp1, ACKENA);        // Write cmd with E high
  i2c_master_write_byte(hnd, tmp,  ACKENA);        // Write cmd with E low
  i2c_master_stop(hnd);                            // End of data for I2C
  err = i2c_master_cmd_begin(I2C_NUM_0, hnd, 10/portTICK_PERIOD_MS); // Send bufferd data to LCD
  i2c_cmd_link_delete (hnd);                       // Link not needed anymore
  if (err != ESP_OK)
     dbgprint ("LCD communication error!");     // Something went wrong (not connected)
}
void print ( char c )
{
  write_data ( c ) ;
}
void scursor ( uint8_t col, uint8_t row )
{
  const int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 } ;
  write_cmd ( COMMAND_SET_DDRAM_ADDR |
              ( col + row_offsets[row] ) ) ; 
}
void sclear()
{
  write_cmd ( COMMAND_CLEAR_DISPLAY ) ;
  delayMicroseconds (3000) ; // Takes longer, to be save 3ms. (tcfkat 20210309)
}
void shome()
{
  write_cmd ( COMMAND_RETURN_HOME ) ;
  delayMicroseconds (3000) ; // Takes longer, to be save 3ms. (tcfkat 20210309)
}
void scroll ( bool son )
{
  uint8_t ecmd = COMMAND_ENTRY_MODE_SET |               // Assume no scroll
                 FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT ;
  if ( son )                                            // Scroll on?
  {
    ecmd |= FLAG_ENTRY_MODE_SET_ENTRY_SHIFT_ON ;        // Yes, change function
  }
  write_cmd ( ecmd ) ;                                  // Perform command
}
void reset()
{
  char buf[20], *p = buf;
//  scommand ( 0 ) ;                                // Put expander to known state
//  delayMicroseconds ( 40000 ) ;
  scommand (0x30);
  delayMicroseconds (4500);
  scommand (0x30);
  delayMicroseconds (100);
  scommand (0x30);
  delayMicroseconds (100);
  scommand (0x20);
  delayMicroseconds (50);
  write_cmd ( COMMAND_FUNCTION_SET |
              FLAG_FUNCTION_SET_MODE_4BIT |
              FLAG_FUNCTION_SET_LINES_2 |
              FLAG_FUNCTION_SET_DOTS_5X8 ) ;
  write_cmd ( COMMAND_DISPLAY_CONTROL |
              FLAG_DISPLAY_CONTROL_DISPLAY_ON ) ;
  sclear();
  write_cmd ( COMMAND_ENTRY_MODE_SET |
              FLAG_ENTRY_MODE_SET_ENTRY_INCREMENT ) ;
  shome();
  for (char c = 'a'; c < 'q'; c++) print (c); // Some test messages: lower case characters
  delay(3000);
  sclear();
  delay(1000);
  shome();
  for (char c = 'A'; c < 'Q'; c++) print (c); // Some test messages: upper case characters
  delay(3000);
  sclear();
  shome();
  sprintf(buf, "Address: 0x%02X", pcf8574address); // Print my own I2C address
  while (*p) print (*p++);
}
void initlcd ( uint8_t sda, uint8_t scl )
{
  i2c_config.mode = I2C_MODE_MASTER,
  i2c_config.sda_io_num = (gpio_num_t) sda ;
  i2c_config.scl_io_num = (gpio_num_t) scl ;
  i2c_config.sda_pullup_en = GPIO_PULLUP_ENABLE; // ENABLE! Not DISABLE! (tcfkat 20210309)
  i2c_config.scl_pullup_en = GPIO_PULLUP_ENABLE; // ENABLE! Not DISABLE! (tcfkat 20210309)
  i2c_config.master.clk_speed = (uint32_t) 100000 ;
  if ( i2c_param_config ( I2C_NUM_0, &i2c_config ) != ESP_OK )
  {
    dbgprint ("param_config error!");
  }
  else if ( i2c_driver_install ( I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0 ) != ESP_OK )
  {
    dbgprint ("driver_install error!");
  }
  pcf8574address = i2c_pcf8574_scan();
  if (pcf8574address){
    dbgprint ("LCD found by bus scan at 0x%02X", pcf8574address);
    reset();
  }else{
    dbgprint ("No LCD found!");
  }
}
void setup()
{
  Serial.begin ( 115200 ) ;                              // For debug
  Serial.println() ;
  initlcd (tft_sda_pin, tft_scl_pin);
  dbgprint("Init done!");
}
void loop()
{
}


Best regards,

Eric

[ - ]
Reply by Elliott_EmbeddedOctober 18, 2021

Hi Eric, thanks for response! 


I notice you have a method that loops through the possible addresses. I know at least that 0x27 is the correct address, since I did the same thing you did in that method a while back (loop through 0x20-0x27 and check which one doesn't cause the ADRACK flag to be set to 1, indicating an acknowledged address). So that at least isn't the issue

I have no oscilloscope, so I'm relegated to just debugging and checking the registers of the board's I2C module I'm using. Bummer. One *extremely* odd thing I've noticed now that must be the problem is that:


When I transmit the slave address, while the controller is busy, if I check the register for the master control status, I get a register value that indicates that the controller is busy, and the bus is busy. Which is what I'd hope to see, and makes sense, since the slave address transmission actually gets acknowledged.


When I try and transmit a command, and check the master control status, at random (I’ve walked through many times with various delays) a given transmission shows that the microcontroller is busy *but* the bus itself is not busy. This confirms my suspicion that not all needed commands were being transmitted during initialization. Is it possible that transmitting with the START STOP RUN flags in the master control register is the wrong way to go about this, and I instead should simply use repeated start conditions? I read that repeated start conditions guarantee at least the master never loses control of the bus, which seems to happening now with the non-repeated START conditions. Generating a STOP condition can cause the master to not be able to utilize the bus. My updated methods look like so, and are based on Arduino's LCD i2C library:

void I2C_write_4_bits(uint8_t byte){            
 expander_write(byte);
 pulse_enable(byte);
}

void send(uint8_t data, int mode){
    char byte_shifted;
    char byte_upper_nibble;
    char byte_lower_nibble;
    byte_shifted = data << 4;
    byte_upper_nibble = data & 0xF0;
    byte_lower_nibble = byte_shifted & 0xF0;
    I2C_write_4_bits(byte_upper_nibble | mode);
    I2C_write_4_bits(byte_lower_nibble | mode);
}

void expander_write(uint8_t data){
    I2C2->MDR = (data | LCD_BACKLIGHT); //the mdr register is where data that will be sent over the data line is stored 
    I2C2->MCS |= 0x07; //0x07 value indicates a STOP, START, RUN transmission: stop flag is generated after data is sent
    while (I2C2->MCS & 0x01);
}
void pulse_enable(uint8_t data){
    expander_write(data | E);
    delay_50_ms();
    expander_write(data & ~E);
    delay_50_ms();
}
void command(uint8_t data){
    send(data,COMMAND);
    delay_50_ms();
}
void data(uint8_t data){
    send(data,DATA);
    delay_50_ms();
}

void I2C_LCD_enable(void){
    I2C_write_4_bits(0x03 << 4);
    delay_50_ms();
    I2C_write_4_bits(0x03 << 4);
    delay_50_ms();
    I2C_write_4_bits(0x03 << 4);
    delay_50_ms();
    I2C_write_4_bits(0x02 << 4);
    command(LCD_FUNCTIONSET | LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS);
    command(LCD_DISPLAYCONTROL | LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF);
    command(LCD_CLEARDISPLAY);
    delay_50_ms();
    command(LCD_ENTRYMODESET | LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT);
    command(LCD_RETURNHOME);
    delay_50_ms();  
}


Is there a possible way to see which error could be causing the bus to be idle during command transmission?

[ - ]
Reply by tcfkatOctober 18, 2021

Hello Elliott,


yes, the autodetect routine is not necessary in your case.

But, something in your I2C-code is totally wrong. You should be able to turn the backlight on and off, this is independent from the HD44780 and concerns only the PCF8574. But, attention, on some PCF8574 piggyback boards there is a jumper that, if removed, disables the backlight permanently.

Maybe I write later something to your code, I have to further read the data sheet of the MCU.

Regards,
Eric

[ - ]
Reply by Elliott_EmbeddedOctober 18, 2021

I notice in your method to send over the command with E low, command with E high, and then command with E low again, you have the following lines:


i2c_master_start(hnd);
  i2c_master_write_byte(hnd, (pcf8574address<<1)|I2C_MASTER_WRITE, ACKENA);
  i2c_master_write_byte(hnd, tmp,  ACKENA);        // Write cmd with E low
  i2c_master_write_byte(hnd, tmp1, ACKENA);        // Write cmd with E high
  i2c_master_write_byte(hnd, tmp,  ACKENA);        // Write cmd with E low
  i2c_master_stop(hnd);                            // End of data for I2C

You don't generate the STOP condition until after you've transmitted the cmd with E low, E high, and E low again. In my code, I was generating STOP conditions like so:


command with E low -> STOP -> command with E high -> STOP -> command with E low


The HD4478 then couldn't understand I was was sending one command and pulsing E since I was STOP'ing after every transmission.


I'm able to turn the backlight off if I send 0b0000 0000 over the line. I'm able to turn it back on if I send 0b 0000 1000 over the line.


However, once I try and send the initialization commands over the line, some commands tell me that the bus is idle while the controller is busy, others tell me the bus and controller are busy when sending data over the line. What would cause this difference? I understand I must send 3 8-bit function set commands (0x03 << 4) before finally setting the mode to 4-bit (0x02 << 4) and then all commands after will require me to send the most significant bit, pulse enable, and then the least significant bit, pulse enable, of each command/data byte.