Elliott Goldstein
Elliott Goldstein

Reputation: 89

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

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:

HD4478 Initialization

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:

enter image description here

I don't think I quite grasp whether to send the initial function set Command to 4-bit mode in 8-bit mode or 4-bit mode, and when the exact changeover to 4-bit mode occurs in the initialization process.

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 : 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);
}
    

I am using the on-board 3.3V power supply from the TI board as my VCC supply.

Upvotes: 1

Views: 262

Answers (1)

jackieBB
jackieBB

Reputation: 125

I think before discussing that the LCD is not working properly, I want to know whether I2C is working properly and whether the Address PIN define.

The address pin of I/O Expander IC is all connected to HIGH, so your Slave address is 0x27

(I just want to make sure whether this part is okay because if there is a problem, there will be problems in the subsequent tests)

enter image description here

If I2C communication work normally, the I/O Expander IC control should be correct.

( Send some commands to see if the IC outputs according to your commands)

If the I/O expander IC work abnormally, maybe you can check the I2C signal use a logic analyzer or oscilloscope to check whether the I2C signal of the MCU is correct. (Slave Address, ACK...etc)

If the above of them is correct, we can start to check the LCD part!

Reminder: Your hyperlink is failed for the LCD and I/O Expander IC Data Sheet.

For the part of Initializing by internal reset circuit

It means that when you supply power to the LCD, it will perform the actions 1~4 below.

enter image description here

During this period, you can use an oscilloscope or logic analyzer to measure the BF pin. It should be HIGH, always When your VCC rises to 4.5V, BF will continue for another 10ms before pulling LOW.

But if VCC does not rise to 4.5V, it must be initialized through MPU.

I saw at the end of the article

I am using the on-board 3.3V power supply from the TI board as my VCC supply.

Does your LCD use 3.3V as the power supply? If yes, you shall initialize LCD by MPU. (But I suggest using the 5V to supply that can reduce the debug time.)

If using the internal initialize function, the LCD setting shall be like below:

  • 8-bit interface
  • 1-line display
  • 5x8 dot character font

So if you want to change to 4-bit mode, need to use the Function set Instruction to set again. (Datasheet P.28)

The signal transmission part must be tested according to the timing diagram

It takes 4 steps to send a command

  1. Transfer upper 4-bit data.
  2. Busy flag check (BF=1)
  3. Busy flag check (BF=0)
  4. Transfer lower 4-bit data

enter image description here

Use the Example of the HD44780U Datasheet for testing (Datasheet P.42)

Step 6, need to be divided into 2 times to send.

enter image description here

And it will show the 'H' on the LCD.

enter image description here

Upvotes: 1

Related Questions