This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C123GH6PM: I2C slave mode sync / delay problem

Part Number: TM4C123GH6PM

Hello,

I am using a timer interrupt to calculate a buffer of 32bytes, and then serving this buffer as i2c slave. The problem is from time to the i2c call from master intervenes with the calculation of buffer, i.e. i start reading tru i2c while the buffer is being formed.

Of course this is a generic programming problem, and I am aware of my options. But however, it is not possible to have i2c interrupt handler waiting, so is there a way to cancel the i2c read callback if a semaphore is busy?

I have an I2C slave handler like:

void I2C0SlaveIntHandler(void) {

    // clear data interrupt
    HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_DATAIC;

    bool _stop = false;
    if(HWREG(I2C0_BASE + I2C_O_SRIS) & I2C_SLAVE_INT_START) {
        // clear start interrupt
        HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_STARTIC;
        // set indices to 0
        writeIndex = 0;
        statusIndex = 0;
        resultIndex = 0;
        ledIndex = 0;
    } else if(HWREG(I2C0_BASE + I2C_O_SRIS) & I2C_SLAVE_INT_STOP) {
        // clear stop interrupt
        HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_STOPIC;
        _stop = true;
    }

If my main timer loop is busy executing calculations, is there any way I can hold the i2c request from master waiting until the calculations are complete, or even is it possible to return an error, or some sort of indication so the master can re-request the packet?

How would you approach this problem. Basically I have an timer loop, doing some work, and I read it with i2c, externally asynchronously.

Best Regards,

C.A.

  • Hello C.A.

    The standard process would be to issue a NACK. You'd need to take manual control of issuing ACK / NACK with the I2CSACKCTL register.

    Effectively while you are processing data, you'd enable the ACK override and have the I2C bus set to always NACK any requests. Then once the data processing is done, you'd either turn off the override or set it to ACK any requests until you need to process data again.

    Best Regards,

    Ralph Jacobi

  • Hello Ralph,

    Is there an example on how to return NACK? Should I just set the ACKOVAL = 1 and ACKOEN = 1, ACKOEN to enable override, and ACJOVAL=1 to return a NACK?

    Something like:

    HWREG(I2C0_BASE + I2CSACKCTL) = ACKOVAL | ACKOEN

    ?

    Best Regards,

    Can

  • Hello Can,

    What you described matches my understanding of how to go about sending a NACK.

    Also while we don't have a dedicated example there are TivaWare APIs for this:

    • I2CSlaveACKOverride
    • I2CSlaveACKValueSet

    You can review those to see the implementation at a DRM level for your application.

    Best Regards,

    Ralph Jacobi

  • Hello Ralph,

    I have added a:

    void I2C0SlaveIntHandler(void) {
    
        // ACK override disable
        HWREG(I2C0_BASE + I2C_O_SACKCTL) &= ~I2C_SACKCTL_ACKOEN;
    
        HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_DATAIC;        // clear data interrupt
    
        if(HWREG(I2C0_BASE + I2C_O_SRIS) & I2C_SLAVE_INT_START) {
            HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_STARTIC;   // clear start interrupt
        } else if(HWREG(I2C0_BASE + I2C_O_SRIS) & I2C_SLAVE_INT_STOP) {
            HWREG(I2C0_BASE + I2C_O_SICR) = I2C_SICR_STOPIC;    // clear stop interrupt
            g_i2c_ready = true;
            return;
        }
    
        switch(I2CSlaveStatus(I2C0_BASE)) {
            case I2C_SLAVE_ACT_NONE:
                break;
            case I2C_SLAVE_ACT_RREQ_FBR:
                byteAddress = I2CSlaveDataGet(I2C0_BASE);
                switch(byteAddress) {
                    case READ_STATUS:
                        if(busy) {
                            // ACK override enable
                            HWREG(I2C0_BASE + I2C_O_SACKCTL) |= I2C_SACKCTL_ACKOEN;
                            // send NACK
                            HWREG(I2C0_BASE + I2C_O_SACKCTL) |= I2C_SACKCTL_ACKOVAL;
                        }
                        break;

    So, when first byte is received, if it is read_status, I return a NACK.

    It does work as expected, but when the busy status goes away, normal (non read_status) calls will return error, once or twice, then return to normal operations.

    I think by sending NACK, we disturb the state of the i2c module. Basically I return NACK after clearing intterrupts. Should I be doing something else as well to reset the sate of the i2c module?

    Best Regards,

    Can

  • Hi Can,

    I'd want to see the state of the I2C bus signals before really trying to give guidance here. Not sure if the master tries to do some sort of error recovery and maybe that's what's throwing things off, or if a NACK at that point is disrupting something. Also in general my lean on I2C is let the master fix any issues as I think its the master that should be driving error recovery. 

    Best Regards,

    Ralph Jacobi

  • Hello Ralph,

    Here are I2C bus signals:

    This is when I get the remote error(121). After that it gives connection timeout(110) for twice, and then 1 io error(5) and then it returns to normal operation.

    Below i the whole shot

    And here is the beginning of it zoomed:

    and the end of it zoomed:

    The SDA line stays low after error, and it is from tm4c123 side, (I have checked it by unplugging sda from master - and wiring the logic analyzer directly from slave tm4c123. after NACK is received, the master continues to do an address read.

    I am using the linux kernels smbus module on a RPI4, tru python, so I dont have low level access to i2c master.

    Best regards,

    Can

  • Hi Can,

    Pardon my lack of understanding with this, but I am not following this comment versus the screenshot:

    This is when I get the remote error(121). After that it gives connection timeout(110) for twice, and then 1 io error(5) and then it returns to normal operation.

    The 121 you mention seems to be the address the master is providing for a read. The other two I don't really follow (particularly the time out twice)?

    I see what you mean about the SDA staying low.

    My first thought is that since you NACK'd the first packet for the data write but the master ignored that and didn't re-try but instead moved to the address read, then your state machine for the I2C handling is now thrown out of wack because you never processed the first packet since you weren't ready previously and the master just kept forging on without trying to reset where you are. I wonder what would happen if you also NACK'd the address read... something to try maybe?

    Best Regards,

    Ralph Jacobi

  • The 123, 110, and 5 are error codes that the master returns. The master is on a RPI, and python smbus library is used for driving the linux kernel i2c module as master.

    I am not sure if we can NACK the address read, since we dont have that state returned from 'I2CSlaveStatus(I2C0_BASE)'

    How do we apply this NACK pattern? Do we send data byte, and then NACK? Should it be performed before and after putting data on the bus?

    I am sure we are doing something wrong, and I have not been able to find any previously done samples in github nor google.

    ---

    Meanwhile, I solved my sync problems by: using crc8 checksum for the 32 byte packet, and when status is read when device is making calculations, it will just return 0's for the data bytes, but proper checksum. Then I made a buffer, where I memcpy my calculation array to this buffer, which gets sent by the i2c slave.

    Then I realized that the i2c read operation of 32bytes takes much more time then memcpying 32 bytes, so occasionally i get 0's in the byte array that gets sent.

    Then I made a python routine that adjust the timer frequency such that the  i2c master will poll the i2c slave only 10ms after it makes its calculations, so I never hit the device when it will be busy.

    I made a workaround, but without using NACK, but I still would like to know how to return NACK.

    Best Regads,

    Can

  • Hi Can,

    Reading into this, I think I see the problem a little differently now. The idea of using a NACK to would hinge on knowing that the packet is one you need to packet ahead of time, and so you NACK the read request. Once you ACK the read request, if you need more time, you could try and implement clock stretching by having the I2C line get held down until you are ready to send data. But otherwise you'd need to know you'd be NACK'ing the read request before it arrives.

    Sending a NACK on a data byte you transmit isn't covered by the I2C protocol from what I know:

    There are five conditions that lead to the generation of a NACK:

    1. No receiver is present on the bus with the transmitted address so there is no device to respond with an acknowledge.

    2. The receiver is unable to receive or transmit because it is performing some real-time function and is not ready to start communication with the master.

    3. During the transfer, the receiver gets data or commands that it does not understand.

    4. During the transfer, the receiver cannot receive any more data bytes.

    5. A master-receiver must signal the end of the transfer to the slave transmitter.

    Best Regards,

    Ralph Jacobi