GrumpyOldPizza / arduino-STM32L4

69 stars 60 forks source link

I2C in slave mode #36

Open dpaxson opened 6 years ago

dpaxson commented 6 years ago

Hi,

I'm trying to set up a Butterfly to act as an I2C slave device. It receives data just fine but trying to send data back in the request callback just ends up having 0's be received on the other end.

I think what is happening is when the callback calls Wire.write, write checks to see if _tx_active is true. If it isn't true, it just early exits, returning 0.

I think one way this could be addressed would be to set _tx_active before calling the request callback from TwoWire::EventCallback and then setting it false again afterwards.

So instead of:

    if (events & I2C_EVENT_TRANSMIT_REQUEST) {
        _tx_write = 0;

        if(_requestCallback) {
        (*_requestCallback)();
        }

        stm32l4_i2c_service(_i2c, &_tx_data[0], _tx_write);
    }

Have something like this:

    if (events & I2C_EVENT_TRANSMIT_REQUEST) {
        _tx_write = 0;
        _tx_active = true;

        if(_requestCallback) {
        (*_requestCallback)();
        }

        _tx_active = false;

        stm32l4_i2c_service(_i2c, &_tx_data[0], _tx_write);
    }
GrumpyOldPizza commented 6 years ago

I think you are correct. Mind giving that a try ?

It seems this had been fixed once, At least the ArduinoCore-stm32l0 has pretty much exactly this fix.

On Thu, Jul 12, 2018 at 8:17 PM, dpaxson notifications@github.com wrote:

Hi,

I'm trying to set up a Butterfly to act as an I2C slave device. It receives data just fine but trying to send data back in the request callback just ends up having 0's be received on the other end.

I think what is happening is when the callback calls Wire.write, write checks to see if _tx_active is true. If it isn't true, it just early exits, returning 0.

I think one way this could be addressed would be to set _tx_active before calling the request callback from TwoWire::EventCallback and then setting it false again afterwards.

So instead of:

if (events & I2C_EVENT_TRANSMIT_REQUEST) { _tx_write = 0;

  if(_requestCallback) {
  (*_requestCallback)();
  }

  stm32l4_i2c_service(_i2c, &_tx_data[0], _tx_write);

}

Have something like this:

if (events & I2C_EVENT_TRANSMIT_REQUEST) { _tx_write = 0; _tx_active = true;

  if(_requestCallback) {
  (*_requestCallback)();
  }

  _tx_active = false;

  stm32l4_i2c_service(_i2c, &_tx_data[0], _tx_write);

}

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/GrumpyOldPizza/arduino-STM32L4/issues/36, or mute the thread https://github.com/notifications/unsubscribe-auth/AG4QfA41Da67u3xhG-a4ciVq9clb1TGKks5uGANQgaJpZM4VOMiX .

dpaxson commented 6 years ago

So, yes, I ended up pulling down the code and applied the change and now I am sending bytes out when in slave mode. It looks like it is working.

dpaxson commented 5 years ago

So I ended up getting a logic analyzer and have been looking at the I2C communications.

I see an I2C write with a single byte (the register to do a read from) followed later by a read. The first pair returns the correct data. The second register write comes through (for a different register) and then the read request. In this case, the same data is returned as for the first request even though it is supposed to be different data. From this second register write forward, it seems that the data that is received by the I2C slave is lagging by one byte. So the 3rd byte received is really the 2nd one that should have been received. The 4th received is really the 3rd that should have been received and so on.

The logic in my program looks to be correct. It looks like there are extra onRequest callback calls being made from the Wire library, however. And there appears to be an onReceive callback call with 0 bytes received before the second register write is received.

Would you have any ideas what might be causing this behavior?

larjohn commented 5 years ago

I 've got a probably related issue with slave I2C. My setup is using a Raspberry Pi Zero W as a master (using pigpio) and a Butterfly equivalent as a slave. Whenever the master is requesting a single byte from the slave, everything is working as expected. If the slave outputs for instance 4 bytes, and the master only requests a single byte, again, everything works.

The issue arises when the slave is requested to write multiple bytes and the master reads all of them. This works the first time: all 4 bytes are transfered successfully. The next time, though, it breaks: the i2cdetect utility can't detect the slave anymore - it needs a board reset to find it. The bytes requested cannot be read by the master (only gets an empty array).

Interestingly enough, while trying to debug it, I modified the TwoWire::EventCallback method like this:

void TwoWire::EventCallback(uint32_t events)
{
    unsigned int status;
    void(*callback)(uint8_t);

    if (_option & I2C_OPTION_ADDRESS_MASK) {
    if (events & I2C_EVENT_RECEIVE_DONE) {
        _rx_read = 0;
        _rx_write = stm32l4_i2c_count(_i2c);

        if(_receiveCallback) {
        (*_receiveCallback)(_rx_write);
        }

        stm32l4_i2c_service(_i2c, &_rx_data[0], BUFFER_LENGTH);
    }

    if (events & I2C_EVENT_RECEIVE_REQUEST) {
        stm32l4_i2c_service(_i2c, &_rx_data[0], BUFFER_LENGTH);
    }

    if (events & I2C_EVENT_TRANSMIT_REQUEST) {
        _tx_write = 0;
        _tx_active = true;

        if(_requestCallback) {
        (*_requestCallback)();
        }

        _tx_active = false;

        for(int i = 0; i < BUFFER_LENGTH; i++)
        {
            Serial.println(_tx_data[i]);
        }

        stm32l4_i2c_service(_i2c, &_tx_data[0], _tx_write);
    }
    } else {
    if (events & (I2C_EVENT_ADDRESS_NACK | I2C_EVENT_DATA_NACK | I2C_EVENT_ARBITRATION_LOST | I2C_EVENT_BUS_ERROR | I2C_EVENT_OVERRUN | I2C_EVENT_RECEIVE_DONE | I2C_EVENT_TRANSMIT_DONE | I2C_EVENT_TRANSFER_DONE)) {
        callback = _completionCallback;
        _completionCallback = NULL;

        if (callback) {
        if (!(events & (I2C_EVENT_ADDRESS_NACK | I2C_EVENT_DATA_NACK | I2C_EVENT_ARBITRATION_LOST | I2C_EVENT_BUS_ERROR | I2C_EVENT_OVERRUN))) {
            status = 0;
        } else {
            if (events & I2C_EVENT_ADDRESS_NACK) {
            status = 2;
            } else if (events & I2C_EVENT_DATA_NACK) {
            status = 3;
            } else {
            status = 4;
            }
        } 

        (*callback)(status);
        }
    }
    }
}

In other words, I just serial print the TX buffer to see what is inside. With that modification, consecutive writes work correctly - which is not expected: the print buffer has to be larger (64 bytes?) than the TX buffer, so the print operation shouldn't be blocking at all, to cause any convenient delay and justify the behavior, implying a race condition.

Please note that, if the master only requests single bytes after the initial multibyte request, it works without modification. Only consecutive multibyte write requests seem to be completely shutting down the I2C channel. I couldn't figure out if this shut-down is handled in the software or if the hardware somehow partially transitions to an unrecoverable state (other parts of my sketch keep working).