Help with I2C (K64F, KSDK 2.0)

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Help with I2C (K64F, KSDK 2.0)

Jump to solution
3,851 Views
robertbaruch
Contributor III

I have a K64F board connected to an Adafruit LCD backpack [schematic] that I have verified works on an Arduino. The backpack is basically an I2C device, an MCP23008 [datasheet]. The backpack is running on 5V.

 

I wrote some code that is supposed to configure I2C0 for port E pins 24 and 25:

 

constexpr port_pin_config_t kI2CPortConfig {     kPORT_PullDisable,     kPORT_FastSlewRate,     kPORT_PassiveFilterDisable,     kPORT_OpenDrainEnable,     kPORT_LowDriveStrength,   kPORT_MuxAlt5,     kPORT_UnlockRegister };   I2C::I2C() {   i2c_master_config_t config;       CLOCK_EnableClock(kCLOCK_PortE);     PORT_SetPinConfig(PORTE, 24, &kI2CPortConfig);     PORT_SetPinConfig(PORTE, 25, &kI2CPortConfig);     I2C_MasterGetDefaultConfig(&config);   I2C_MasterInit(I2C0, &config, CLOCK_GetBusClkFreq()); }   status_t I2C::send(uint8_t address, const uint8_t *txBuff, size_t txSize) const {   status_t status = I2C_MasterStart(I2C0, address, kI2C_Write);   if (status != kStatus_Success) {   return status;   }     status = I2C_MasterWriteBlocking(I2C0, txBuff, txSize);   if (status != kStatus_Success) {   return status;   }     return I2C_MasterStop(I2C0); }              

 

I'm sending data like this:

 

  uint8_t set_defaults_msg[] = {       kReg_iodir,    0xFF, // set to all inputs    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // all other registers to zero   };     i2c_.send(i2c_addr_, set_defaults_msg, 11);     

 

Now, I hooked up an analyzer to the I2C0 pins, and got this:

 

141850_141850.jpgi2c.jpg

 

So there are a few questions about this:

 

1. The analyzer shows the clock is going at 277 kHz, not 100 kHz. Am I using the right clock in I2C_MasterInit?

2. The analyzer shows the clock going between 0 and 3.something volts. But if I configured the ports as open drain, and the backpack has a 4.7k pullup to 5v, why isn't the output going to 5v?

3. Should I be using the functions in drivers/fsl_i2c.h or platform/i2c/drivers/master/fsl_i2c_master_driver.h?

4. I2C_MasterStart is returning kStatus_I2C_Busy. I'm guessing because SDA is low. But I'm not convinced the backpack is doing that. I disconnected the backpack and replaced it with a pair of 4.7k resistors going to +5v, and the same thing happened. So what could be causing the busy status?

Labels (1)
Tags (3)
0 Kudos
1 Solution
1,411 Views
susansu
NXP Employee
NXP Employee

Hi Robert,

  • Divider table change in SDK 2.0

About the divider table, we have simplified it in SDK 2.0, because we found that the table({ICR, SCL divider}) is {0x00, 20}, {0x01, 22}, {0x02, 24},{0x03, 26}....., which shows that the ICR is increasing by 1 at each time. So we remove the ICR from the table and make the ICR as index of table(s_i2cDividerTable) only containing SCL divider {20,22,24,26....}, an example: s_i2cDividerTable[0] = 20, s_i2cDividerTable[1] = 22, now the index (0, 1....) is just the ICR. That's why there is following code:

            if (absError < bestError)             {                 bestMult = mult;                 bestIcr = i; /* the index of the s_i2cDriverTable is ICR. */                 bestError = absError;

                /* If the error is 0, then we can stop searching because we won't find a better match. */                 if (absError == 0)                 {                     break;                 }             }

  • Baudrate issue

The I2C is using bus clock for K64 clock driver, I do not have such issue running the K64F I2C examples , may you please try the i2c_read_accel_value_transfer example?

  • Code issue in your application

I have went through your code and found an issue here, that's why the data you saw on the scope is not right:

The I2C_MasterStart is an non-blocking API, which means calling the API just check whether the bus is busy, if it's not busy, just send out a start signal and the address to the I2C->D register, and the API directly returns without waiting for the addess sent. If user use such functional API, user needs to wait until the address sent mannually in the application, so the right calling flow for your application shoud be

    status= I2C_MasterStart(I2C0, address, kI2C_Write);

    /* Return if error. */     if (status != kStatus_Success)     {         return status;     }

    uint8_t hw_status;

    /* Wait until address transfer complete. */

    while (!((hw_status = I2C0->S) & kI2C_IntPendingFlag))

    {

    }

    /* Check if there's error during address transfer. */

    if (hw_status & kI2C_ArbitrationLostFlag)

    {

        /* Clear arbitration lost flag. */

        I2C0->S = kI2C_ArbitrationLostFlag;

        status = kStatus_I2C_ArbitrationLost;

    }

    /* Check NAK */

    else if (hw_status & kI2C_ReceiveNakFlag)

    {

        status = kStatus_I2C_Nak;

    }

    if (status != kStatus_Success)

    {

        I2C_MasterStop(I2C0);

    }

    I2C_MasterWriteBlocking(I2C0, txBuff, txSize);

    .......

And I strongly suggest you to just call an I2C_MasterTransferBlocking to ease your application.

status_t I2C::send(uint8_t address, const uint8_t *txBuff, size_t txSize) const {      i2c_master_transfer_t masterXfer;

    memset(&masterXfer, 0, sizeof(masterXfer));

    masterXfer.slaveAddress = address;     masterXfer.direction = kI2C_Write;     masterXfer.subaddress = 0;     masterXfer.subaddressSize = 0;     masterXfer.data = txBuff;     masterXfer.dataSize = txSize;     masterXfer.flags = kI2C_TransferDefaultFlag;

    return I2C_MasterTransferBlocking(I2C0, masterXfer); }

View solution in original post

0 Kudos
7 Replies
1,412 Views
susansu
NXP Employee
NXP Employee

Hi Robert,

  • Divider table change in SDK 2.0

About the divider table, we have simplified it in SDK 2.0, because we found that the table({ICR, SCL divider}) is {0x00, 20}, {0x01, 22}, {0x02, 24},{0x03, 26}....., which shows that the ICR is increasing by 1 at each time. So we remove the ICR from the table and make the ICR as index of table(s_i2cDividerTable) only containing SCL divider {20,22,24,26....}, an example: s_i2cDividerTable[0] = 20, s_i2cDividerTable[1] = 22, now the index (0, 1....) is just the ICR. That's why there is following code:

            if (absError < bestError)             {                 bestMult = mult;                 bestIcr = i; /* the index of the s_i2cDriverTable is ICR. */                 bestError = absError;

                /* If the error is 0, then we can stop searching because we won't find a better match. */                 if (absError == 0)                 {                     break;                 }             }

  • Baudrate issue

The I2C is using bus clock for K64 clock driver, I do not have such issue running the K64F I2C examples , may you please try the i2c_read_accel_value_transfer example?

  • Code issue in your application

I have went through your code and found an issue here, that's why the data you saw on the scope is not right:

The I2C_MasterStart is an non-blocking API, which means calling the API just check whether the bus is busy, if it's not busy, just send out a start signal and the address to the I2C->D register, and the API directly returns without waiting for the addess sent. If user use such functional API, user needs to wait until the address sent mannually in the application, so the right calling flow for your application shoud be

    status= I2C_MasterStart(I2C0, address, kI2C_Write);

    /* Return if error. */     if (status != kStatus_Success)     {         return status;     }

    uint8_t hw_status;

    /* Wait until address transfer complete. */

    while (!((hw_status = I2C0->S) & kI2C_IntPendingFlag))

    {

    }

    /* Check if there's error during address transfer. */

    if (hw_status & kI2C_ArbitrationLostFlag)

    {

        /* Clear arbitration lost flag. */

        I2C0->S = kI2C_ArbitrationLostFlag;

        status = kStatus_I2C_ArbitrationLost;

    }

    /* Check NAK */

    else if (hw_status & kI2C_ReceiveNakFlag)

    {

        status = kStatus_I2C_Nak;

    }

    if (status != kStatus_Success)

    {

        I2C_MasterStop(I2C0);

    }

    I2C_MasterWriteBlocking(I2C0, txBuff, txSize);

    .......

And I strongly suggest you to just call an I2C_MasterTransferBlocking to ease your application.

status_t I2C::send(uint8_t address, const uint8_t *txBuff, size_t txSize) const {      i2c_master_transfer_t masterXfer;

    memset(&masterXfer, 0, sizeof(masterXfer));

    masterXfer.slaveAddress = address;     masterXfer.direction = kI2C_Write;     masterXfer.subaddress = 0;     masterXfer.subaddressSize = 0;     masterXfer.data = txBuff;     masterXfer.dataSize = txSize;     masterXfer.flags = kI2C_TransferDefaultFlag;

    return I2C_MasterTransferBlocking(I2C0, masterXfer); }

0 Kudos
1,411 Views
robertbaruch
Contributor III

Thanks -- I see now that the tables in SDK1.3 and SDK2.0 are actually the same, it's just that the table in SDK1.3 was not ordered.

I'll try the example and your code, thanks!

0 Kudos
1,411 Views
robertbaruch
Contributor III

OK, one mystery is solved. By debugging the i2c polling example and my program, I found that my program was constructing the I2C instance during static initialization, which is exactly what I wanted. But the clock isn't actually set to its final value until BOARD_BootClockRUN is called, and I called that in main, which runs *after* static initialization. Oops. So I really was, in essence, using the wrong clock.

I changed the mechanism of initialization to construct-on-first-use, and used dependency injection to have I2C initialization depend on board initialization, and now the I2C frequency is correct :smileyhappy:

0 Kudos
1,411 Views
robertbaruch
Contributor III

And the last mystery is resolved, I did need a level shifter between the 3v3 K64F board and the 5v backpack device.

0 Kudos
1,411 Views
robertbaruch
Contributor III

I'm wondering if there's a bug in fsl_i2c.c. It seems to use the wrong bestIcr (it uses the index into the divider table, not the ICR). Here's the code from fsl_i2c.c (KSDK 2.0):

        /* Scan table to find best match. */

        for (i = 0u; i < sizeof(s_i2cDividerTable) / sizeof(uint16_t); ++i)

        {

            computedRate = srcClock_Hz / (multiplier * s_i2cDividerTable[i]);

            absError = baudRate_Bps > computedRate ? (baudRate_Bps - computedRate) : (computedRate - baudRate_Bps);

            if (absError < bestError)

            {

                bestMult = mult;

                bestIcr = i;

                bestError = absError;

                /* If the error is 0, then we can stop searching because we won't find a better match. */

                if (absError == 0)

                {

                    break;

                }

            }

        }

And here's the code from fsl_i2c_hal.c (KSDK 1.3.0):

    /* Search for the settings with the lowest error.

     * mult is the MULT field of the I2C_F register, and ranges from 0-2. It selects the

     * multiplier factor for the divider. */

    for (mult = 0u; (mult <= 2u) && (bestError != 0); ++mult)

    {

        multiplier = 1u << mult;

        /* Scan table to find best match.*/

        for (i = 0u; i < ARRAY_SIZE(kI2CDividerTable); ++i)

        {

            computedRate = sourceClockInHz / (multiplier * kI2CDividerTable[i].sclDivider);

            absError = hz > computedRate ? hz - computedRate : computedRate - hz;

            if (absError < bestError)

            {

                bestMult = mult;

                bestIcr = kI2CDividerTable[i].icr;

                bestError = absError;

                /* If the error is 0, then we can stop searching

                 * because we won't find a better match.*/

                if (absError == 0)

                {

                    break;

                }

            }

        }

    }

If you compare s_i2cDividerTable (fsl_i2c.c) with kI2CDividerTable (fsl_i2c_hal.c) you'll see that the dividers are the same and in the same order, but the ICRs are not present in fsl_i2c.c like they are in fsl_i2c_hal.c

0 Kudos
1,411 Views
robertbaruch
Contributor III

I'm using the FRDM-K64F board.

The baud rate is set to 100kHz by the code in I2C_MasterGetDefaultConfig. That's why I suspect that the clock I'm supplying in the call to I2C_MasterInit might be wrong...

Thanks for pointing out that note. I wish there were an equivalent diagram of the output circuits in the datasheet. I think what's happening is there's a Zener diode between the output and VDD, so even if I pull the output up to 5V, it gets clamped to VDD. I have some level shifters on order, that will make the bus 3v3 compliant on the K64 side, and 5v compliant on the backpack side.

As for the analyzer, the waveform you see is the very first activity on the i2c lines, so that should be the START signal. I suspect that the 3v3 logic high output is not enough to trigger the 5v backpack side. Hopefully the level shifter will help.

0 Kudos
1,411 Views
kerryzhou
NXP TechSupport
NXP TechSupport

1. The I2C sample code for K64f in KSDK2.0 is for the FRDM board or the tower board, could you tell me which board you are using?FRDM-K64 or TWR-K64?

  Your baud rate is not 100KHZ, did you configure the baud rate to 100Khz?

  You can use the KSDK2.0 sample code directly:SDK_2.0_FRDM-K64F\boards\frdmk64f\driver_examples\i2c\polling_transfer

This code have the 100Khz baud rate.

2. If you check the K64 datasheet, you will find the open drain pullup voltage leave is VDD, the vdd is 1.771-3.6V, so it can't be pull up to 5V.

18.jpg

3.Both the function in KSDK2.0 about the I2C can be used, but fsl_i2c_master_driver.h is the KSDK1.3.0 code, if you are using KSDK2.0, you should use the KSDK2.0 driver instead of the KSDK1.3.0.

4. When you use the I2C_MasterStart, did you use the analyzer check the wave, whether the I2C send the START signal in the I2C bus or not?

I2C_MasterStart is used to initiate a new master mode transfer by sending the START signal.

Besides, when you use this function, did you do the stop in the bus before? If the STOP signal never happens, then the START send again, the bus may be busy.


Have a great day,
Jingjing

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos