When I did this, I also did not read the busy status, and just relied on delays as necessary.
I did however notice that the various LCD controller specs that I consulted said that it was necessary for the RS and R/W lines to be valid for some time before raising E, so I transmitted an extra byte if required:
// pins on PCF8574A
const uint8_t lcd_rs = 0x01; // LOW: instruction/command. HIGH: data.
const uint8_t lcd_rw = 0x02; // LOW: write to LCD. HIGH: read from LCD.
const uint8_t lcd_enable = 0x04; // HIGH = enable (latch at fall back to LOW)
const uint8_t lcd_backlight = 0x08;
uint8_t lcd_idata[5] = {0xff}; // up to 5 i2C bytes to send 1 byte to LCD
uint8_t lcd_light; // 0 or lcd_backlight bit on
HAL_StatusTypeDef I2C_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// One i2c byte has to be sent for each change of state of the lcd lines.
// E has to be raised and lowered to clock out the data, however, LCD controller specs
// say that the RS and R/W lines have to be valid anywhere between 40 to 150nS BEFORE
// raising E, hence an additional i2c byte may be required initially, keeping E low.
// (currently, R/W is always low)
void lcd_wi4(uint8_t c) // 4 bits to lcd instruction/command register
{
uint8_t l,i=0;
l = c<<4 | lcd_light; // shift lower 4 bits to correct positions
// Last byte sent kept in first byte of lcd_idata. Check if RS (or R/W) have changed:
if ( (l&(lcd_rs|lcd_rw)) != (lcd_idata[0]&(lcd_rs|lcd_rw)) )
lcd_idata[i++] = l; // ENABLE=0, RS=0, R/W=0
lcd_idata[i++] = l|lcd_enable; // ENABLE=1, RS=0, R/W=0
lcd_idata[i++] = l; // ENABLE=0, RS=0, R/W=0
I2C_Transmit (&hi2c1, i2c_lcd, lcd_idata, i, 100);
lcd_idata[0] = l; // keep for next time
}
void lcd_wi8(uint8_t c) // 8 bits to lcd instruction/command register
{
uint8_t u,l,i=0;
u = (c&0xf0) | lcd_light; // leaves upper 4 bits in correct positions
l = c<<4 | lcd_light; // shift lower 4 bits to correct positions
// Last byte sent kept in first byte of lcd_idata. Check if RS (or R/W) have changed:
if ( (u&(lcd_rs|lcd_rw)) != (lcd_idata[0]&(lcd_rs|lcd_rw)) )
lcd_idata[i++] = u; // ENABLE=0, RS=0, R/W=0
lcd_idata[i++] = u|lcd_enable; // ENABLE=1, RS=0, R/W=0
lcd_idata[i++] = u; // ENABLE=0, RS=0, R/W=0
lcd_idata[i++] = l|lcd_enable; // ENABLE=1, RS=0, R/W=0
lcd_idata[i++] = l; // ENABLE=0, RS=0, R/W=0
I2C_Transmit (&hi2c1, i2c_lcd, lcd_idata, i, 100);
lcd_idata[0] = l; // keep for next time
}
void lcd_wd8(uint8_t c) // 8 bits to lcd DD ram
{
uint8_t u,l,i=0;
u = (c&0xf0) | lcd_rs | lcd_light; // leaves upper 4 bits in correct positions
l = c<<4 | lcd_rs | lcd_light; // shift lower 4 bits to correct positions
// Last byte sent kept in first byte of lcd_idata. Check if RS (or R/W) have changed:
if ( (u&(lcd_rs|lcd_rw)) != (lcd_idata[0]&(lcd_rs|lcd_rw)) )
lcd_idata[i++] = u; // ENABLE=0, RS=1, R/W=0
lcd_idata[i++] = u|lcd_enable; // ENABLE=1, RS=1, R/W=0
lcd_idata[i++] = u; // ENABLE=0, RS=1, R/W=0
lcd_idata[i++] = l|lcd_enable; // ENABLE=1, RS=1, R/W=0
lcd_idata[i++] = l; // ENABLE=0, RS=1, R/W=0
I2C_Transmit (&hi2c1, i2c_lcd, lcd_idata, i, 100);
lcd_idata[0] = l; // keep for next time
}
I never tried it without that extra check, so maybe that is not necessary.