1

I have stm32f4-discovery kit and I want to try i/o expander for hd44870 LCD . I have PCF8574AT link to io example like mine 8-bit expander where i2c address is 0x3f (checked by i2c scanner) on hi2c3 hardware. For c/c++ use HAL libraries on Eclipse environment. Ok take look at code.

First I initialize i2c3 - like Datasheet 100kHz on SCL:

static void MX_I2C3_Init(void)
{

  hi2c3.Instance = I2C3;
  hi2c3.Init.ClockSpeed = 100000;
  hi2c3.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c3.Init.OwnAddress1 = 0;
  hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c3.Init.OwnAddress2 = 0;
  hi2c3.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c3.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c3) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

Then try to send data to I/O expander. But before that I check that i/o is ready to use:

result = HAL_I2C_IsDeviceReady(&hi2c3,0x3f , 2, 2);
if (result == HAL_BUSY)
        {
          HD44780_Puts(6, 1, "busy");
        }else{
            HD44780_Puts(6, 1, "ready");
            uint8_t data_io = 0xff;
            HAL_I2C_Master_Transmit(&hi2c3, 0x3f, data_io, 1, 100);
        }

On a same expander nothing changes. Any ideas what is wrong or maybe i/0 expander is broken ?

Bulkin
  • 1,020
  • 12
  • 27
PawelW
  • 21
  • 1
  • 6
  • What scanner did you use? Arduino based? – Bulkin Nov 19 '17 at 12:42
  • If you use Arduino to scan I2C, that's not correct. I2C address is HIGH 7 bits and 1 bit of READ/WRITE flag. Arduino lib shifts address itself and puts flag bit also. But at STM32 you have to do that manually. If you write to I2C, address must be `0x3F << 1 | 0x01 = 7F` – Bulkin Nov 20 '17 at 12:06

2 Answers2

0

Im not sure about HAL driver, really never used HAL. But I have touched pcf8574 IO expander. As you said, you have checked it with scanner and if you get address, line and device is OK. As I am not expert on I2C and HAL libs,I'll show my I2C driver it relies on STM32 standard periphery drivers and it worked for PCF8574 and various I2C devices. There is an example,snippet(blocking mode, not irq based):

  1. Checking if IO is not busy.

    while(I2C_GetFlagStatus(&I2Cx, I2C_FLAG_BUSY) == SET){
    if((timeout--) == 0) return -ETIMEDOUT;
    

    }

  2. Generate start condition and set write condition(with address for write mode).

    I2C_TransferHandling(&I2Cx, dev_addr, 1, I2C_SoftEnd_Mode, I2C_Generate_Start_Write);
    while(I2C_GetFlagStatus(&I2Cx, I2C_ISR_TXIS) == RESET){
        if((timeout--) == 0) return -ENODEV;
    }
    
  3. Now you can send data byte( it is your IO states), This function writes directly to I2C TX(transceiver) register :

    I2C_SendData(&I2Cx, reg_addr);
    while(I2C_GetFlagStatus(&I2Cx, I2C_ISR_TC) == RESET){
        if((timeout--) == 0) return -EIO;
    }
    
  4. Generate reading condition and than read from PCF8574, data should be same as it was just written(if nothing toggles IO expander). Basically you can read byte or more bytes (depends on device). In your case PCF8574(8bit) gives only 1 byte.

    I2C_TransferHandling(dev->channel,dev_addr, len,  I2C_AutoEnd_Mode,I2C_Generate_Start_Read);
    size_t i;
    for(i=0;i<len;i++){
        timeout = I2C_TIMEOUT;
        while(I2C_GetFlagStatus(dev->channel, I2C_ISR_RXNE) == RESET){
            if((timeout--) == 0) return -EIO;
        }
    
        data[i] = I2C_ReceiveData(dev->channel);
    }
    
  5. You can continue RW operations, or just simply wait till device automatically stop transition on line:

    while(I2C_GetFlagStatus(&I2Cx, I2C_FLAG_STOPF) == RESET){
        if((timeout--) == 0) return -EIO;
    }
    
    I2C_ClearFlag(&I2Cx, I2C_ICR_STOPCF);
    

This steps will write and read data. Anyway this chip has some tricky logic there, it more simplistic than it looks like.Actually it works just as simple OUTPUT. Extern input just triggers up PCF8574 pin and nothing more, no special configuration for input mode. For input monitor for sure use PCF8574 INT pin, PCF8574 will trigger INT pin.

For example:

If you want input pins, than just simply set pins to logic zero. And monitor INT pin,if change happens on input, INT pin will be triggered and you should read data via I2C .

For OUTPUT low just write zero's.

And for OUTPUT high set logic true.

You are using HAL so you have to read what happens inside HAL_I2C_Master_Transmit function. Do not forget that address is 7bit and first byte with address also includes R/W condition.First byte bit0 is R/W bit. So you should handle it.

for example with defines:

#define PCF8574_WRITE_ADRESS  (0x40) /*for writing to chip*/
#define PCF8574_READ_ADRESS  ((0x40)|0x01) /*for reading chip*/

Here is some links: i2c explanations

this may help

Really nice guide!

Hope this will help to understand your problem and solve it.:)

David.O
  • 104
  • 5
0

thanks , Bulkin

I found obvious mistake . HAL libs do not i2c_address << 1 . I/YOU must put that in code not same result !

HAL_I2C_Master_Transmit(&hi2c3, (0x3f<<1), data_io, 1, 100);

or

$define i2c_address_write (0x3f <<1 )
HAL_I2C_Master_Transmit(&hi2c3, i2c_address_write , data_io, 1, 100);

to read :

$define i2c_address_read ((0x3f <<1) | 0x01)
HAL_I2C_Master_Transmit(&hi2c3, i2c_address_read , data_io, 1, 100);
PawelW
  • 21
  • 1
  • 6