cembeddedi2cbeagleboneblack

I2C Peripheral fails after a single transaction


I am using this library to sample some sensors over the I2C bus using the PRUs on the Beaglebone Black. It seems as if only the first transaction works correctly, after which the library seems to only attempt to read from register address 0xFF, followed by the SCL line being pulled low (see attached image captures by logic analyzer). I have already opened an issue on their github, but am not expecting an answer there as the repository hasn't been updated in years and the author hasn't been active on GitHub since 2019.

logic analyzer output

While trying to find the problem myself, I decided to write the register address and received data in the shared memory to inspect using prudebug. For this test, I am attempting to read registers 0x00 and 0x03. Interestingly, it writes the correct register addresses into memory (see image), but only the first returned value is correct (0xEA). The value written into the received data buffer in shared memory is 0xFF, which does not make sense. As can be seen from the logic analyzer, 0xFF is the address it attempts to read from.

memory dump

I don't know what I am doing wrong here, but any help would be appreciated! My main code is shown below, and the library functions can be found in the linked repository here.

uint8_t curr_data_idx = 0;
uint8_t tmp = 0;
uint8_t reg = 0x00;
volatile uint8_t * buf = ((volatile uint8_t*)(RESERVED_SMEM_ADDR + 32));

int main(void)
{

    // enable OCP
    CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;

    pru_i2c_driver_DelayMicros(500000);

    uint8_t res = 0;
    res = pru_i2c_driver_Init(1);
    uint8_t saddr = 0x68;

    pru_i2c_driver_ReadReg(1, saddr, reg, &tmp);
    buf[curr_data_idx] = tmp;
    pru_i2c_driver_DelayMicros(1000);
    curr_data_idx++;

    reg = 0x03;
    pru_i2c_driver_ReadReg(1, saddr, reg, &tmp);
    buf[curr_data_idx] = tmp;
    pru_i2c_driver_DelayMicros(1000);
    curr_data_idx++;
   
    __halt();
    return 0;
}

Solution

  • I have found the issue with the given code after some tedious debugging; turns out the delays given in the library were not enough, and the library failed to clear the bit flags that indicate an error occurred. As a result, the first transaction would occur as normal, but when attempting to read the data from the RX FIFO, the AERR flag would be set since the FIFO was still empty when trying to read due to the delays not being enough. Then, when trying to perform the second read, the error flag is still set and the data read from the RX FIFO is actually the previous value. After adding a few more microseconds of delay between transactions and making sure to handle any errors that occur everything works well. The updated ReadBytes function from the library I've rewritten to:

    uint8_t pru_i2c_driver_ReadBytes(
                      const uint8_t i2cDevice, 
                      const uint8_t address, 
                      const uint8_t reg, 
                      const uint8_t bytes, 
                      uint8_t* buffer, 
                      i2c_flags_t* flags
    )
    {
        uint8_t tries = 0;
    _init_section:
        if(!pru_i2c_initialized[i2cDevice-1]) {
            if(!pru_i2c_driver_Init(i2cDevice)) {
                *flags |= I2C_FAILED_INIT;
                if(tries++ < MAX_TRIES){
                    pru_i2c_driver_DelayMicros(1);
                    goto _init_section;
                }
                return 0;
            }
        }
        tries = 0;
    
    _write_init:
        if (!pru_i2c_driver_WaitBB(i2cDevice))
        {
            *flags |= I2C_FAILED_WAIT_BB;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(1);
                goto _write_init;
            }
            return 0;
        }
        tries = 0;
    
        CT_I2C[i2cDevice-1]->I2C_SA_bit.I2C_SA_SA = address; // 7 bit address
        CT_I2C[i2cDevice-1]->I2C_CNT_bit.I2C_CNT_DCOUNT = 1; // 1 byte to transmit
        CT_I2C[i2cDevice-1]->I2C_CON = 0x8601; // EN/MST/TRX/STT
        pru_i2c_driver_DelayMicros(7);
    
    
    _write_start:
        if (!pru_i2c_driver_WaitXRDY(i2cDevice))
        {
            *flags |= I2C_FAILED_WAIT_XRDY;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(1);
                goto _write_start;
            }
            return 0;
        }
    
        // write register to read
        CT_I2C[i2cDevice-1]->I2C_DATA = reg;
        CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_XRDY = 0b1;
    
        // wait access to registers
        if (!pru_i2c_driver_WaitARDY(i2cDevice))
        {
            *flags |= I2C_FAILED_WAIT_ARDY;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(1);
                goto _write_start;
            }
            return 0;
        }
    
        pru_i2c_driver_DelayMicros(5);
        CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_ARDY = 0b1;
    
        if (CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_AERR)
        {
            *flags |= I2C_AERR_WRITE;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(1);
                goto _write_start;
            }
            *flags |= I2C_AERR_WRITE_UNRESOLVED;
            return 0;
        }
        if(CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_NACK)
        {
            *flags |= I2C_NACK_DETECTED;
            CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_NACK = 0b1;
            return 0;
        }
    
        tries = 0;
    _read_init:
        // read data
        CT_I2C[i2cDevice-1]->I2C_CNT_bit.I2C_CNT_DCOUNT = bytes; // bytes to receive
        CT_I2C[i2cDevice-1]->I2C_CON = 0x8403; // EN/MST/STP/STT
        pru_i2c_driver_DelayMicros(20);
    
    _read_start:
        // wait data
        if (!pru_i2c_driver_WaitRRDY(i2cDevice))
        {
            *flags |= I2C_FAILED_WAIT_RRDY;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(1);
                goto _read_start;
            }
            return 0;
        }
    
        pru_i2c_driver_DelayMicros(10);
    
        int8_t count;
    
        for (count = 0; count < bytes; count++)
        {
        _looptop:
            // read byte
            buffer[count] = CT_I2C[i2cDevice-1]->I2C_DATA;
    
            pru_i2c_driver_DelayMicros(10);
    
            if (CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_AERR)
            {
                CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_AERR = 0b1;
                if(tries++ < MAX_TRIES){
                    pru_i2c_driver_DelayMicros(10);
                    goto _looptop;
                }
                return 0;
    
            }
            if (CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_NACK)
            {
                *flags |= I2C_NACK_DETECTED;
                CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_NACK = 0b1;
                return 0;
            }
    
        _wait_next_data:
            // require next data
            CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_RRDY = 0b1;
    
            // wait data
            if (!pru_i2c_driver_WaitRRDY(i2cDevice))
            {
                *flags |= I2C_FAILED_WAIT_RRDY;
                if(tries++ < MAX_TRIES){
                    pru_i2c_driver_DelayMicros(10);
                    goto _wait_next_data;
                }
                return 0;
            }
    
            tries = 0;
        }
    
    _transaction_end:
        // wait for access ready
        if (!pru_i2c_driver_WaitARDY(i2cDevice))
        {
            *flags |= I2C_FAILED_WAIT_ARDY;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(10);
                goto _transaction_end;
            }
            return 0;
        }
    
    _wait_bus_free:
        // wait for bus free
        // wait data
        if (!pru_i2c_driver_WaitBF(i2cDevice))
        {
            *flags |= I2C_FAILED_WAIT_BF;
            if(tries++ < MAX_TRIES){
                pru_i2c_driver_DelayMicros(10);
                goto _wait_bus_free;
            }
            return 0;
        }
    
        CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_ARDY = 0b1;
        CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_XRDY = 0b1;
        CT_I2C[i2cDevice-1]->I2C_IRQSTATUS_RAW_bit.I2C_IRQSTATUS_RAW_RRDY = 0b1;
    
        return count;
    }
    

    Furthermore, the delay function was plain wrong. It did not delay the requested microseconds and was optimized away by the compiler as soon as optimizations were turned on. I fixed it by changing it to:

    void DelayMicros(uint8_t micros){
        // Factor of 29 gives most accurate timings verified with logic analyzer
        uint16_t cycles = ((uint16_t)micros * 29u);
        uint16_t i = 0;
    #pragma MUST_ITERATE(29, , 29)
    #pragma UNROLL(1)
        for (i = 0; i < cycles; i++)
        {
            __delay_cycles(1);
        };
    }