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.
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.
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;
}
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);
};
}