Forums

Interfacing STM32F429 with HD4478 LCD that has PCF8574T IO Expander Backpack using I2C

Started by Elliott_Embedded 5 months ago11 replieslatest reply 5 months ago81 views

Hi everyone, having a bit of an issue that is making me want to pull my hair out. I am trying to write from an STM32F429ZI to an LCD that has the PCF8574T IO Expander using I2C. Because I am in 4-bit mode, transmitting 8-bytes requires 4 transmisisons like so: My code works in such a way that, since I am in 4-bit mode, sending one byte of data over the SDA line will take 4 tranmissions: 1. The upper nibble of the data with the Enable Pin High 2 Upper Nibble with Enable Pin Low 3. Lower NIbble with Enable Pin High 4. Lower Nibble with Enable Pin Low My code for transmitting commands and data is written as such. Register Select's (RS) pin must be set to low for command transmission and set to high for data transmission.

void LCD_sendCommand(char command)

{

    uint8_t upperNibble = 0;

    uint8_t lowerNibble = 0;

    //obtain the upper/lower 4 bits of the command to send out

    upperNibble |= (command & 0xF0);

    lowerNibble |= ((command << 4) & 0xF0);

    //for the first send of each nibble, we want EN to be high. We will set it back to low on the second transmission

    gSendBuffer[0] = upperNibble | BACKLIGHT | EN;

    gSendBuffer[1] = (upperNibble | BACKLIGHT) & ~EN;

    gSendBuffer[2] = lowerNibble | BACKLIGHT | EN;

    gSendBuffer[3] = (lowerNibble | BACKLIGHT) & ~EN;

    if(HAL_I2C_Master_Transmit(&hi2c1,PCF8574T_ADDRESS,(uint8_t *)gSendBuffer,4,TRANMISSION_WAIT_TIMEOUT)!=HAL_OK) {

        return;

    }

}

void LCD_sendData(char data)

{

    uint8_t upperNibble = 0;

    uint8_t lowerNibble = 0;

    //obtain the upper/lower 4 bits of the data to send out

    upperNibble |= (data & 0xF0);

    lowerNibble |= ((data << 4) & 0xF0);

    //buffer content:

            //upper nibble with BACKLIGHT, EN, and RS set high (RS is high for command)

           //upper nibble with BACKLIGHT and RS set high (EN is now set low so we can pulse the enable pin)

           //lower nibble with BACKLIGHT, EN, and RS set high (RS is high for command)

           //lower nibble with BACKLIGHT and RS set high (EN is now set low so we can pulse the enable pin)

    gSendBuffer[0] = upperNibble | BACKLIGHT | EN | RS;

    gSendBuffer[1] = (upperNibble | BACKLIGHT | RS) & ~EN;

    gSendBuffer[2] = lowerNibble | BACKLIGHT | EN | RS;

    gSendBuffer[3] = (lowerNibble | BACKLIGHT | RS) & ~EN;

    if(HAL_I2C_Master_Transmit(&hi2c1,PCF8574T_ADDRESS,(uint8_t *)gSendBuffer,4,TRANMISSION_WAIT_TIMEOUT)!=HAL_OK) {

        return;

    }

}


 I respect the initialization routine to enable the LCD to 4-bit mode based on this source: http://web.alfredstate.edu/faculty/weimandn/lcd/lc...

It proceeds as such:

void LCD_initialize()

{

    HAL_Delay(200);

    //0x30

    LCD_sendCommand(functionSet8Bit1Line5x7Dots);

    HAL_Delay(10);

    //0x30

    LCD_sendCommand(functionSet8Bit1Line5x7Dots);

    HAL_Delay(10);

    //0x30

    LCD_sendCommand(functionSet8Bit1Line5x7Dots);

    HAL_Delay(10);

    //0x20

    LCD_sendCommand(functionSet4Bit1Line5x7Dots);

    HAL_Delay(10);

    //0x28

    LCD_sendCommand(functionSet4Bit2Line5x7Dots);

    HAL_Delay(10);

    //display off, cursor off, blinking off

    //0x08

    LCD_sendCommand(displayOffCursorOff);

    HAL_Delay(10);

    //0x01

    LCD_sendCommand(clearDisplay);

    HAL_Delay(4);

    //entry mode set: curosr increments from left to right, display does not shift

    //0x06

    LCD_sendCommand(entryMode);

    HAL_Delay(10);

    //turns display, cursor off, blink off

    //0x0F

    LCD_sendCommand(displayOnCursorOnBlinkOn);    

}


 I am pulling up the SDA and SCL lines with 5K Ohm resistors each. Based on my logic analyzer output that data being transmitted corresponds exactly to what I should be seeing. Consider the following screenshot for a send of command 0x30:

logicpic_73095.png

The upper nibble of 0x30 is 0b0011 and the lower nibble is 0b0000. Therefore the 4 transmissions are: 1.0b0011 | (1 << 2) | (1 << 3) = 0b0011 1100 = 60 = 0x3C 2.0b0011 | (1 << 3) = 0b0011 1000 = 56 = 0x38 3.0b0000 | (1 << 2) | (1 << 3) 0b0000 1100 = 0x0C 4.0b0000 | (1 << 3) 0b0000 1000 = 0x08 Which is exactly what the logic analyzer sees.

If I try and send the character 'A', whose ascii code is 65, my 4 transmissions are: Each transmission also adds up here (logic is the same as command sending, but here the RS pin, (1 << 0), is also set high because we are sending data:

lcdpic2_10309.png


However, despite all this logic analyzer output that would seem to imply that transmission is being received and acknowledged, absolutely nothing happens with the LCD. The backlight is on of course, but the transmission of the initialization sequence and the character 'A' has no effect on it. What could I possibly be missing here? I'm kind of lost at this point as to how how to keep debugging when all seems right.





[ - ]
Reply by CustomSargeJanuary 30, 2022

In this "neo-natal surgery" scenario, I'd write some atomic line set/reset code. I write asm, so I'm clueless on your code. But be deliberate in having data, r/w* & d/c* set then Only toggle e, while keeping the others stable.

Edit: also check the PCF spec to be certain consecutive writes don't have a "burp / glitch" on the outputs.

Also, they wake up in 8 bit mode, you have to init it to 4 bit, just checking. Most controllers I've dealt with want 3 consecutive initializations of mode, with a fairly long time between each (check your spec, it may also want big mS after powerup).

I thought about doing this as a shoehorn for constrained projects, but realized if I needed expanded I/O, using it to access the LCD is a really poor choice. Direct outputs and/or inputs - fine, but an object that has timing and a protocol?  P.S. good luck using the busy status signal.

Not to rain on the parade, but I'd find a different hardware arrangement. I hope you're not "stuck" with these components.  Good Hunting  <<<)))


[ - ]
Reply by Elliott_EmbeddedJanuary 30, 2022

>Also, they wake up in 8 bit mode, you have to init it to 4 bit, just checking. Most controllers I've dealt with want 3 consecutive initializations of mode, with a fairly long time between each (check your spec, it may also want big mS after powerup).

Yup made sure to ensure all this. Made 3 consecutive 8-bit initialization calls after a 200 MS delay before I make my first 4-bit initialization command.

>also check the PCF spec to be certain consecutive writes don't have a "burp / glitch" on the outputs

Good idea. Currently I'm making just one call to the hardware abstraction layer I2C transmit API method, and it sends out all 4 bytes in one call. I could test with seeing if 4 separate calls makes any difference






>Not to rain on the parade, but I'd find a different hardware arrangement. I hope you're not "stuck" with these components.  Good Hunting  <<<)))

Haha, I actually meant to buy a normal HD4478 but accidentally bought this one without looking at it too close at my local Micro Center. Now it's just a pride thing that I can't let this thing beat me because I've seen a lot of tutorials out there where it is possible to do it. 





[ - ]
Reply by CustomSargeJanuary 31, 2022

Didn't know it's a bought module and known functional.

You hit on something - block send. Note there are commands that want ~40uS to be accepted, the block send may easily goof that. Clear, Home, etc are that way too. Doing separate sends is a solid idea. G.H <<<)))

[ - ]
Reply by DilbertoJanuary 30, 2022

A silly question:

Is the LCD contrast adjust ok?

[ - ]
Reply by Elliott_EmbeddedJanuary 30, 2022

Hmm, good catch, looking at the wiki page for the HD44780 it states:

"

  1. Contrast adjustment (VO) This is an analog input, typically connected to a potentiometer. The user must be able to control this voltage independent of all other adjustments, in order to optimise visibility of the display that varies i.a. with temperature, and, in some cases height above the sea level. With a wrong adjustment the display will seem to malfunction."

    The issue seems to be that with my IO expander I only have access to pins D4-D7 and the RS/RW/EN/BACKLIGHT pins. Any ideas on how I could test the potentiometer out and for what I should be looking for? For what it's worth, it's a 5V power supply powering the LCD
[ - ]
Reply by tasbihmrJanuary 30, 2022

In your code, what is the value of PCF8574T_ADDRESS ? Is the address you have setup correct ? 

[ - ]
Reply by Elliott_EmbeddedJanuary 30, 2022

It's 0x4E. Based on STM32 I2C addressing this would be correct I believe. The address of the device is 0x27, but STM32 addressing shifts this address one bit to the left since it reserves the lowest bit is read/write functionality. So the upper 7 bits are the actual address, and leads to (0x27 << 1) = address is 0x4E

[ - ]
Reply by DilbertoJanuary 30, 2022

Addressing should be correct, otherwise, you'd not have ACKs, as seen in the analyzer plots.

About the busy flag, I'd not use it for the first approach, because it comprises the control of data flow direction, which is a complication factor. 

Use timeout. After it's working, try to implement a handshake via busy flag.

Even if you are doing it for pride, perhaps it would be a good idea to test with a common LCD and an I/O expander separately and check the data in the connections between the I/O expander and the LCD.

[ - ]
Reply by Steve_WheelerJanuary 30, 2022

I agree that LCD contrast is a good thing to look into; it's an easy thing to get wrong.

Other than that, it's been several years since I've written LCD code for that controller, but I've done it for several 8- and 16-bit SBCs. Our hardware designer did not bring back the BUSY signal from the LCD (don't ask me why not), so we had to use hard-coded delays, which is what you've got in your code.

I don't know how tightly you coded your timing delays, but one "gotcha" that we ran into was that code that just exceeded the specified minimum times for inter-character and post-command delays would work properly for 2-line displays, but wasn't quite long enough for 4-line displays. 

Display hardware may have improved since then, but if you're using a 4-line display, it may be worth extending your delays a bit.

[ - ]
Reply by Bob11January 31, 2022

From your post it's not clear to me what HAL_Delay() really is. These 44780 controllers tend to be SLOW SLOW SLOW, and you don't want to move faster than the BUSY bit (DB7). If you're not reading the BUSY bit on DB7 but using delays, make sure your delays are long enough. For reference, the Linux 44780 driver uses 120uS delays for commands and 45uS delays after data transfers.

I also agree with Dilberto to make sure your contrast adjust is OK.

[ - ]
Reply by Elliott_EmbeddedJanuary 31, 2022

HAL_Delay() is a millisecond delay. My delays are pretty generous for the requirements on the datasheet of the LCD currently