Interfacing STM32F429 with HD4478 LCD that has PCF8574T IO Expander Backpack using I2C
Started by 3 years ago●11 replies●latest reply 3 years ago●184 viewsHi 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:
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:
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.
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 <<<)))
>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.
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 <<<)))
A silly question:
Is the LCD contrast adjust ok?
Hmm, good catch, looking at the wiki page for the HD44780 it states:
"
- 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
In your code, what is the value of PCF8574T_ADDRESS ? Is the address you have setup correct ?
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
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.
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.
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.
HAL_Delay() is a millisecond delay. My delays are pretty generous for the requirements on the datasheet of the LCD currently