AnsweredAssumed Answered

Kinetis KL I2C driver

Question asked by Marco Palestro on Mar 5, 2020
Latest reply on Mar 10, 2020 by Diego Charles

Hi,

I'm trying to write a simple interrupt driven I2C driver on Kinetis KL05; I've spent days browsing for examples but I couldn't come up with working code; while it seems to be transmitting and generating the interrupt, it always fails loosing arbitration (while there is only one other device on the I2C bus).

Is there anybody willing to post barebone code for driving I2C device?

I've seen many references to a kinetis_I2C.h written by Mark Butcher for "uTasker", but it's more than 1000 lines (!!!) and so much convoluted that I couldn't find a clue about it.

I've found another driver written by Jan Rychter, and since it's a lot less convoluted than the other, I could copy the operations used, but there is still something wrong.

Here is my code (also in attachment):

 

#include "i2c.h"
#include "IO_Map.h"


#define I2C_AVAILABLE             0
#define I2C_BUSY                 1
#define I2C_ERROR1                 2
#define I2C_ERROR2                 3
#define I2C_ERROR3                 4

 

#define I2C_WRITING             0
#define I2C_READING             1

 

struct
{
    uint8_t status;
    uint8_t tx_length;
    uint8_t txrx;
    uint8_t index;
    uint16_t* rx_data;
    uint8_t buffer[I2C_MAX_MSG_SIZE];
} channel;

 

static void enable_irq(uint32_t irq)
{
    NVIC_ISER |= (1 << irq);
}

 


// -----------------------------------------------------------------------------
// i2c_init
//
// -----------------------------------------------------------------------------
void i2c_init()
{
    // turn on i2c module
    SIM_SCGC4 |= SIM_SCGC4_I2C0_MASK;
    SIM_SCGC5 |= SIM_SCGC5_PORTA_MASK;

 

    // set ports function to i2c
    PORTA_PCR3 = (uint32_t) (PORT_PCR_MUX(0x02));                                            // SCL
    PORTA_PCR4 = (uint32_t) (PORT_PCR_MUX(0x02) | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK);        // SDA

 

    // enable interrupt for i2c (INT_I2C0 = 0x18 = 24)
      enable_irq(INT_I2C0 - 16);

 

    I2C0_C1 = 0;
    I2C0_C1 |= I2C_C1_IICEN_MASK;    // enable module operation

 

    uint8_t mult = 0x00;    // 0x00=1,0x01=2,0x10=4

 

    // see 36.4.1.10 I2C divider and hold values
    // icr=0x20 scl divider=160
    // I2C baud rate = bus speed (Hz)/(mul × SCL divider)
    // baud = bus / 320
    uint8_t icr = 0x20;
    I2C0_F &= ~0xf;
    I2C0_F |= ((mult << I2C_F_MULT_SHIFT) | icr);

 

    channel.status = I2C_AVAILABLE;

 

    I2C0_S |= I2C_S_IICIF_MASK;                             // clear interrupt service flag
    I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK);        // enable module, enable interrupt
    I2C0_C1 |= (I2C_C1_MST_MASK | I2C_C1_TX_MASK);            // set as master, start tx
}

 

// -----------------------------------------------------------------------------
// i2c_message
//
// -----------------------------------------------------------------------------
bool i2c_message(const uint8_t* msg, uint8_t length, uint16_t* rx_data)
{
    // - send i2c message using interrupts
    // - if msg requires read, copy received data (2 bytes) on rd_data if specified
    // - return false if i2c busy

 

    if (channel.status != I2C_AVAILABLE)
    {
        return false;
    }

 

    if (length > I2C_MAX_MSG_SIZE)
    {
        return false;
    }

 

    channel.status    = I2C_BUSY;
    channel.txrx      = I2C_WRITING;
    channel.tx_length = length;
    channel.index     = 0;
    channel.rx_data   = rx_data;

 

    // copy message to local buffer
    uint8_t i;
    for (i = 0; i < length; i++)
    {
        channel.buffer[i] = msg[i];
    }

 

    I2C0_S |= I2C_S_IICIF_MASK;                             // clear interrupt service flag
    I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK);        // enable module, enable interrupt
    I2C0_C1 |= (I2C_C1_MST_MASK | I2C_C1_TX_MASK);            // set as master, start tx

 

    uint8_t status = I2C0_S;                                // check status register
    if (status & I2C_S_ARBL_MASK)                            // lost arbitration
    {
        // stop i2c
        I2C0_S |= I2C_S_ARBL_MASK;                             // clear arbl (by setting it)
        I2C0_C1 &= ~(I2C_C1_IICIE_MASK | I2C_C1_MST_MASK | I2C_C1_TX_MASK);
        channel.status = I2C_ERROR1;
        return false;
    }

 

    // tx first byte
    I2C0_D = channel.buffer[channel.index];
    channel.index++;

 

    return true;
}

 

 

 

// -----------------------------------------------------------------------------
// isr_i2c_chiam
//
// -----------------------------------------------------------------------------
void isr_i2c_chiam()
{
    uint8_t status = I2C0_S;                                // check status register

 

    if (!(status & I2C_S_IICIF_MASK))
    {
        // check that i2c isf flag is set
        return;
    }

 

    I2C0_S |= I2C_S_IICIF_MASK;                             // clear interrupt service flag

 

    if (status & I2C_S_ARBL_MASK)                            // lost arbitration
    {
        // stop i2c
        I2C0_S |= I2C_S_ARBL_MASK;                             // clear arbl (by setting it)
        I2C0_C1 &= ~(I2C_C1_IICIE_MASK | I2C_C1_MST_MASK | I2C_C1_TX_MASK);
        channel.status = I2C_ERROR2;
        return;
    }

 

      if (channel.txrx == I2C_WRITING)
      {
        if (channel.index == channel.tx_length)
        {
            // end of tx: check if read must be performed
            if (channel.rx_data != 0)
            {
                // switch to reading
                channel.txrx = I2C_READING;
                channel.index = 0;
                I2C0_C1 &= ~I2C_C1_TX_MASK;         // switch to RX mode
                I2C0_C1 &= ~(I2C_C1_TXAK_MASK);      // ACK all but the final read

 

                // dummy read
                *(channel.rx_data) = I2C0_D;
            }
            else
            {
                // Generate STOP (set MST=0), switch to RX mode, and disable further interrupts
                I2C0_C1 &= ~(I2C_C1_MST_MASK | I2C_C1_IICIE_MASK | I2C_C1_TXAK_MASK);
                channel.status = I2C_AVAILABLE;
            }
            return;
        }

 

        if (status & I2C_S_RXAK_MASK)
        {
              // NACK received: generate a STOP condition and abort
            I2C0_C1 &= ~(I2C_C1_MST_MASK | I2C_C1_IICIE_MASK);     // generate STOP and disable further interrupts
            channel.status = I2C_ERROR3;
            return;
        }

 

        // write byte
        I2C0_D = channel.buffer[channel.index];
        channel.index++;
    }
    else
    {
        if (channel.index == 0)
        {
            // first read
            I2C0_C1 |= I2C_C1_TXAK_MASK;         // do not ACK the final read

 

            *(channel.rx_data) = I2C0_D;        // low byte first?
            channel.index++;
        }
        else
        {
            // second and last read

 

            // All the reads in the sequence have been processed (but note that the final data register
            // read still needs to be done below!) Now the next thing is the end of a sequence; we need
            // to switch to TX mode to avoid triggering another I2C read when reading the contents of
            // the data register
            I2C0_C1 |= I2C_C1_TX_MASK;

 

            // Perform the final data register read now that it's safe to do so
            uint8_t rx = I2C0_D;
            *(channel.rx_data) |= (rx << 8);    // high byte second?

 

            // Generate STOP (set MST=0), switch to RX mode, and disable further interrupts
            I2C0_C1 &= ~(I2C_C1_MST_MASK | I2C_C1_IICIE_MASK | I2C_C1_TXAK_MASK);
            channel.status = I2C_AVAILABLE;
        }
    }
}

Outcomes