Part Number: TM4C1230H6PM
Hello, I'm trying to send some characters to the screen of an HD4478 LCD that has an I2C backpack. The LCD itself is attached to a 5V power supply from an Arduino. I am attempting to perform initialization on the LCD by using the internal reset circuit so I can set all of it's operations to 4-bt mode since the I2C expander will condense the number of data bits that can be sent at once from 8 to 4. Here's an image from the LCD datasheet describing the process:

Furthermore, I'm trying to follow the instructions using the internal reset circuit to set the mode to 4-bit mode with 2 lines of space for characters and 5x10 font with a blinking cursor that enters from the left side of the screen. Here's the datasheet page that describes the process:

The issue is currently when I attempt to execute these instructions in the order described I see nothing on the LCD, only that it's backlight is on.
The first weird issue I ran into was initializing the appropriate GPIO_PORTE pins (4/5) to act as I2C pins for the I2C2 module. I had to manually use the HWREG macro with the GPIO_O_PUR register to initialize a standard weak pull up resistor to both pins. I tried using GPIOPadConfigSet() with GPIO_PIN_TYPE_STD_WPU, which I thought would do the exact same thing, but I found different behaviors. Calling that function would not set my clock and data lines to high before I began transmission, and nothing would send (I tested this by sending 0x00, which just turns off the backlight). Using HWREG with GPIO_O_PUR actually set the data and clock lines high before transmission, and sending 0x00 this way would actually turn off the backlight:
void GPIOE_enable()
{
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE);
GPIOPinConfigure(GPIO_PE4_I2C2SCL);
GPIOPinConfigure(GPIO_PE5_I2C2SDA);
GPIOPinTypeI2C(GPIO_PORTE_BASE,GPIO_PIN_5);
GPIOPinTypeI2CSCL(GPIO_PORTE_BASE,GPIO_PIN_4);
HWREG(GPIO_PORTE_BASE + GPIO_O_PUR) = (HWREG(GPIO_PORTE_BASE + GPIO_O_PUR) | GPIO_PIN_4 | GPIO_PIN_5);
}
I initialized my I2C2 module here:
void I2C_enable()
{
SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C2);
SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
I2CMasterEnable(I2C2_BASE);
I2CMasterInitExpClk(I2C2_BASE,SysCtlClockGet(),false);
I2CMasterSlaveAddrSet(I2C2_BASE,0x27,false);
}
The biggest part of my confusion, and where I think I could be going wrong, is how to format the data I'm sending over to the LCD. I unfortunately do not have a scope or logic analyzer, but I know based on the fact I can transmit 0x00 and it will turn the backlight off *something* must be able to go over the lines. Since I am using an I2C I/O expander, I am constricted to sending just 4 bits at a time. With the HD4478 LCD, there exists an EN (enable) pin. Pulsing this (sending the data/command byte with EN high, and then sending again with EN low) will cause the HD4478 to "lock in" this data/command byte and actually execute it. However, now I am sending an upper and lower nibble of each byte at a time. This has made me confused- if I'm sending an upper and lower nibble, when do I pulse enable? Currently I have it configured such that I pulse EN for the high nibble, and then pulse EN for the low nibble of every byte I send. This means every byte now requires, in my code, 4 transmissions- the upper nibble with E high, the upper nibble with E low, the lower nibble with E high, and the lower nibble with E low. This seems like it could be incorrect but I'm not sure the right way to go about it. Another part of this I'm unsure about is the usage of BURST_SEND_START/CONT/STOP. Every time I transmit the 4 transmissions that represent a singular byte, the upper nibble with E high (first transmission) has parameter 2 of I2CMasterControl set to I2C2_MASTER_CMD_BURST_SEND_START, and the final transmission, the lower nibble with E low, set to I2C_MASTER_CMD_BURST_SEND_STOP
Here are the methods that describe this transmission method:
//send upper 4 bits of command with E set to High, and with backlight on
//Since this is the beginning of the new command, parameter 2 of I2CMasterControl is set to "BURST_SEND_START"
void expander_write_upper_nibble(uint8_t data)
{
I2CMasterDataPut(I2C2_BASE,PULSE_HIGH);
I2CMasterControl(I2C2_BASE,I2C_MASTER_CMD_BURST_SEND_START);
while (I2CMasterBusy(I2C2_BASE) & I2CMasterBusBusy(I2C2_BASE));
}
//send lower 4 bits of command with E set to High, and with backlight on
//Since this is the beginning of the new command, parameter 2 of I2CMasterControl is set to "BURST_SEND_CONT"
void expander_write_lower_nibble(uint8_t data)
{
I2CMasterDataPut(I2C2_BASE,PULSE_HIGH);
I2CMasterControl(I2C2_BASE,I2C_MASTER_CMD_BURST_SEND_CONT);
while (I2CMasterBusy(I2C2_BASE) & I2CMasterBusBusy(I2C2_BASE));
}
//send command with E set to low and with back light on
void pulse_enable_upper_nibble(uint8_t data)
{
I2CMasterDataPut(I2C2_BASE,PULSE_LOW);
I2CMasterControl(I2C2_BASE,I2C_MASTER_CMD_BURST_SEND_CONT);
}
//after sending the lower nibble with the EN high, send again with EN low (pulsing enable)
//this is the final data we should be sending to the shift register. We can pass the send state as
// I2C2_MASTER_CMD_BURST_SEND_STOP
void pulse_enable_lower_nibble(uint8_t data)
{
I2CMasterDataPut(I2C2_BASE,PULSE_LOW);
I2CMasterControl(I2C2_BASE,I2C_MASTER_CMD_BURST_SEND_STOP);
}
//send upper 4 bits w/E high and then send w/ E low
void I2C_write_upper_nibble(uint8_t byte)
{
expander_write_upper_nibble(byte);
pulse_enable_upper_nibble(byte);
}
//write lower nibble pulse enable
void I2C_write_lower_nibble(uint8_t byte)
{
expander_write_lower_nibble(byte);
pulse_enable_lower_nibble(byte);
}
//write upper 4 bits + pulse, write lower 4 bits + pulse E
void send(uint8_t data, int mode)
{
uint8_t byte_shifted;
uint8_t byte_upper_nibble;
uint8_t byte_lower_nibble;
byte_shifted = data << 4;
byte_upper_nibble = data & 0xF0;
byte_lower_nibble = byte_shifted & 0xF0;
I2C_write_upper_nibble(byte_upper_nibble | mode);
I2C_write_lower_nibble(byte_lower_nibble | mode);
}
//RS set 0
void command(uint8_t data)
{
send(data,0);
}
//RS set to 1
void data(uint8_t data)
{
send(data,1);
}
Finally, here is my LCD_init method which attempts to follow the instructions for the internal reset circuit
/*initializationCommands[0] = 4 bit mode / 2 Lines / 5 x 10 dot display
[1] = cursor on / display on / blink on (0x08 + 0x04 + 0x02 + 0x01)
[2] = Enter cursor left
*/
void I2C_LCD_init()
{ uint8_t i;
uint8_t initializationCommands[3] = {0x30,0xE,0x06};
//12.5 ms delay
delay();
I2CMasterDataPut(I2C2_BASE,0x20 | E | LCD_BACKLIGHT);
while (I2CMasterBusy(I2C2_BASE) & I2CMasterBusBusy(I2C2_BASE));
I2CMasterDataPut(I2C2_BASE,(0x20 & ~ E) | LCD_BACKLIGHT);
I2CMasterControl(I2C2_BASE,I2C_MASTER_CMD_BURST_SEND_STOP);
while (I2CMasterBusy(I2C2_BASE) & I2CMasterBusBusy(I2C2_BASE));
for(i = 0; i<3; i++) {
send(initializationCommands[i],0);
}
}
And finally, for even more clarity, the definitions for the commands that can be sent to the HD4478 LCD:
#define LCD_CLEARDISPLAY 0x01 #define LCD_RETURNHOME 0x02 #define LCD_ENTRYMODESET 0x04 #define LCD_DISPLAYCONTROL 0x08 #define LCD_CURSORSHIFT 0x10 #define LCD_FUNCTIONSET 0x20 #define LCD_SETCGRAMADDR 0x40 #define LCD_SETDDRAMADDR 0x80 // flags for display entry mode #define LCD_ENTRYRIGHT 0x00 #define LCD_ENTRYLEFT 0x02 #define LCD_ENTRYSHIFTINCREMENT 0x01 #define LCD_ENTRYSHIFTDECREMENT 0x00 // flags for display on/off control #define LCD_DISPLAYON 0x04 #define LCD_DISPLAYOFF 0x00 #define LCD_CURSORON 0x02 #define LCD_CURSOROFF 0x00 #define LCD_BLINKON 0x01 #define LCD_BLINKOFF 0x00 // flags for display/cursor shift #define LCD_DISPLAYMOVE 0x08 #define LCD_CURSORMOVE 0x00 #define LCD_MOVERIGHT 0x04 #define LCD_MOVELEFT 0x00 // flags for function set #define LCD_8BITMODE 0x10 #define LCD_4BITMODE 0x00 #define LCD_2LINE 0x08 #define LCD_1LINE 0x00 #define LCD_5x10DOTS 0x04 #define LCD_5x8DOTS 0x00 // flags for backlight control #define LCD_BACKLIGHT 0x08 #define LCD_NOBACKLIGHT 0x00