Kinetis I2C keeps losing arbitration

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

Kinetis I2C keeps losing arbitration

5,619 Views
galadragos
Contributor III

The I2C module in the kinetis series is the most stubborn i2c module I have ever worked with!

No matter the speed settings it keeps losing arbitration (WTF?).

There are no other masters on the bus, so there should not be an arbitration problem.

I need a working example using i2c interrupts to transfer data, all examples I have seen are polled.

The MCU is KL25Z from the freedom board and there should be no repeated start issue as the MULT bits are set to 0b00.

All development is done bare metal style, without PE.

0 Kudos
23 Replies

2,564 Views
DustyStew
Contributor V

Mark

Nice lot of code there, though quite a lot of indirection and so not immediately obvious what the initialization settings are at the register level. I have a full driver written as well, and it has been working, but apparently I changed something and broke it. I'm suspecting something in the MCG or PORT initialization. Or something dead obvious that I am overlooking. While I design hardware at the gate level, the documentation is too soft to be able to reason with. It appears there are some timing dependencies built into the I2C state machine that are undocumented. So while code can work, that doesn't mean it is correct. Apparently that was the case with my code. 

0 Kudos

2,565 Views
mjbcswitzerland
Specialist V

Thomas

Yes there is a lot of code but 1/3 is for selecting the correct pins depending on device and option, as well as recovering from a dead-lock situation. Plus of course circular buffer management etc.

There is some indirection register access from the original Coldfire driver, which has been used in > 100 projects in the last 8 years, which I would code a bit diffferently if reworked, but they say "don't try to fix the Jeep if it aint broken" so it has been left.

Also there is some I2C simulation code which allows the Kinetis I2C controller operation and slave devices to be emulated to simplify project development and testing. When working with the simulator the written registers are also seen so this means that there is also no degredation in maintenance requirements as it is. Usually code is written with the simulator operating so each new line of code is them immediately tested as soon as it is entered.

Note however that I don't think I have ever experienced an arbitration loss at a master with only slaves (neither with a Coldfire nor a Kinetis) so am interested to hear what causes it in your case. The only port initialisation of importance is to set the open drain mode (for parts which don't have pseudo-open drains and so no additional configuration requirement) and the I2C is clocked from the bus clock so I also don't know of clock setup dependencies.

Regards

Mark

0 Kudos

2,565 Views
galadragos
Contributor III

Kinetis L devices don't have open drain ports. At least not the ones that I am using (KL46 and KL25).

Wrong clock settings can cause weird behaviour, KL25Z will not generate repeated start pulses if the F[MULT] bits are not 0.

My particular problem was caused by not generating a stop condition after reading bytes from i2c. Had to set C1[TX] before reading last byte.

0 Kudos

2,565 Views
mjbcswitzerland
Specialist V

Dragos

>>Kinetis L devices don't have open drain ports. At least not the ones that I am using (KL46 and KL25).

Correct, L parts have pseudo-open-drains when the pins are configured for I2C so don't need the same open-drain setting as the K parts (the code can however still set the open-drain flag - it just won't do anything).

>>Wrong clock settings can cause weird behaviour, KL25Z will not generate repeated start pulses if the F[MULT] bits are not 0.

This is the "standard" e6070 errata that has existed from day 1 for most, if not all, parts. Thesse bits should be 0 but this is generally not a restriction. I still see no relation to the clock module (MCG) though.

Regards

Mark

0 Kudos

2,565 Views
joey81
Contributor II

Hi Mark,

I have a home-brew I2C driver for the 150MHz K60.  I'm talking to an Intersil digital potentiometer (ISL22343).

The write routine works OK.  We can see the potmeter appropriately responding to what we write to it.  Reading back is a different story.  We can see the the data being clocked out from the ISL22343 at the 4th frame.read-sequence.jpg

However, the value we get from the I2C1_D register is always 0xFF, no matter what value the ISL spits out.

I don't have open drain enabled (ODE=0) on both SCL and SDA.

When I enable ODE (just like what the generated code of Processor Expert), it gets worse:  I get an arbitration lost error (ARBL=1) as soon as MST is set to 1.  Nothing comes out of the I2C pins.

I made a small project with nothing but the PE-generated code for I2C.  It also gets the same ARBL error.

BTW, I have CW Dev't studio v10.5.

TIA!

-Joey

0 Kudos

2,563 Views
mjbcswitzerland
Specialist V

Joey

It is difficult to imagine what would cause always 0xff to be seen at reception - I think that showing the code may help.

I don't have much experience with PE generated I2C code apart from debugging a case with reading accelerometer values each time its INT triggered: FRDM-K20D50M-Accelerometer Interrupt issue

During the tests I didn't have any major problems with the I2C itself but the code was a bit mashed up in my opinion (using blocking routines with interrupts), which made actual program design around it tricky if the internal workings are not correctly understood.

There is an I2C driver in the uTasker project which has been used on many different K and KL parts without any known issue in case you are looking for a solution which I believe will avoid any further problems: KINETIS Project Code

It also allows the I2C interface and slave devices to be simulated. The MAX543X (digital potentioment) is included in the slave device simulator and it is quite easy to add new ones if needed. I added the list of present devices below.

Regards

Mark

http://www.utasker.com/kinetis.html

/**************************************************************************/

/*                      M24M01 EEPROM                                     */

/**************************************************************************/

/**************************************************************************/

/*                  Dallas DS1621 Temperature sensor                      */

/**************************************************************************/

/**************************************************************************/

/*                  National LM75A Temperature sensor                     */

/**************************************************************************/

/**************************************************************************/

/*                  Dallas DS1307 RTC                                     */

/**************************************************************************/

/**************************************************************************/

/*                      PCF2129A RTC                                      */

/**************************************************************************/

/**************************************************************************/

/*                  Dallas DS3640 RTC and secure memory                   */

/**************************************************************************/

/**************************************************************************/

/*       STMPE811 port expander with touch screen controller              */

/**************************************************************************/

/**************************************************************************/

/*               SHT21 temperature and humidity sensor                    */

/**************************************************************************/

/**************************************************************************/

/* MAX3353 USB OTG Charge Pump with switchable Pullup/Pulldown resistors  */

/**************************************************************************/

/**************************************************************************/

/*                  Wolfson WM8510 Audio Codec                            */

/**************************************************************************/

/**************************************************************************/

/*                  MAX543X Digital Potentiometer                         */

/**************************************************************************/

/**************************************************************************/

/*            NATIONAL LM80 Microprocessor System Hardware Monitor        */

/**************************************************************************/

/**************************************************************************/

/*                    PHILIPS PCF8574 8 bit Port Expander                 */

/**************************************************************************/

/**************************************************************************/

/*                    Freescale MMA8451Q 3-axis accelerometer             */

/**************************************************************************/

/**************************************************************************/

/*                    Freescale FXOS8700 6-axis sensor                    */

/**************************************************************************/

/**************************************************************************/

/*                   Freescale MMA7660F 3-axis accelerometer              */

/**************************************************************************/

/**************************************************************************/

/*                       NXP PCF8575 16 bit Port Expander                 */

/**************************************************************************/

/**************************************************************************/

/*                         NXP PCA9539 Port Expander                      */

/**************************************************************************/

0 Kudos

2,562 Views
joey81
Contributor II

Here's the initialization code:

void i2c_initialize()

{

    /* SIM_SCGC4: IIC1=1 */

    SIM_SCGC4 |= SIM_SCGC4_IIC1_MASK;                                                  

    /* I2C1_C1: IICEN=0,IICIE=0,MST=0,TX=0,TXAK=0,RSTA=0,WUEN=0,DMAEN=0 */

    I2C1_C1 = 0x00U;                     /* Clear control register */

    /* I2C1_S: TCF=0,IAAS=0,BUSY=0,ARBL=0,RAM=0,SRW=0,IICIF=1,RXAK=0 */

    I2C1_S = I2C_S_IICIF_MASK;           /* Clear interrupt flag */

    PORTE_PCR1 = PORT_PCR_MUX(6);    // I2C1_SCL

    PORTE_PCR0 = PORT_PCR_MUX(6);    // I2C1_SDA

   

//    PORTE_PCR1 |= PORT_PCR_ODE_MASK;    // enable open drain

//    PORTE_PCR0 |= PORT_PCR_ODE_MASK;    // enable open drain

   

    /* I2C1_C2: GCAEN=0,ADEXT=0,HDRS=1,SBRC=0,RMEN=0,AD=0 */

    I2C1_C2 = I2C_C2_HDRS_MASK;                                                  

    /* I2C1_FLT: ??=0,??=0,??=0,FLT=0 */

    I2C1_FLT = 0x00U;                    /* Set glitch filter register */

    /* I2C1_SMB: FACK=0,ALERTEN=0,SIICAEN=0,TCKSEL=0,SLTF=1,SHTF1=0,SHTF2=0,SHTF2IE=0 */

    I2C1_SMB = I2C_SMB_SLTF_MASK;                                                  

    /* I2C1_F: MULT=0,ICR=0x33 */

    I2C1_F = I2C_F_MULT(0) | I2C_F_ICR(0x33);            /* Set prescaler bits */

    I2C1_C1 |= I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK;        // enable I2C module

   

} /* i2c_initialize */

With this code, I get 0xFF

When I uncomment lines 13 and 14 (I understand ODE is a must), I get an arbitration lost error as soon as the K60's I2C module is set to master.

0 Kudos

2,562 Views
mjbcswitzerland
Specialist V

Hi

The initialisation looks fine.

You do have pull-up resistors on the I2C bus don't you?

Regards

Mark

0 Kudos

2,562 Views
jrychter
Contributor V

For what it's worth, I recently wrote a small, self-contained, standalone (e.g. runs on bare metal) I2C driver for the Kinetis I2C module. It is interrupt-driven and supports repeated start. I intend to release it as open source as soon as it gets some testing. So far I only used it on the K20 FRDM board with the MMA845x accelerometer there. I wrote it because I couldn't find one. I find it both amusing and sad that recently all I do is write I2C drivers for every platform I touch, because I can't find a decent piece of code anywhere. I already did this for the MSP430: (I2C using USI on the MSP430) and Linux (Lsquaredc (L²C)).

It *should* work on all Kinetis devices, also those that have multiple I2C modules (like the KL25), but it hasn't been tested much. Also, it only supports master mode and 7-bit addressing. It is not intended to solve every possible use case, just provide a small and robust basic functionality.

If people are interested in trying it out before it is better tested, let me know — I can put it on github sooner rather than later.

0 Kudos

2,564 Views
DustyStew
Contributor V

It's somewhat intermittent, that is, it doesn't generate the loss of arbitration error every time. I am wondering what is happening internally in terms of clock. Or whether this could have something to do with PORT or GPIO module initialization. It is unstable. 

0 Kudos

2,564 Views
mjbcswitzerland
Specialist V

Thomas

Try writing 0x90 followed by 0xb0 (no delay needed between the two).

Then write an address to the data register.

Then wait for the interrupt flag to be set before writing 0x90 again.

I think your problem is that you aren't setting the transmit mode before trying to send a start condition.

Regards

Mark

0 Kudos

2,564 Views
DustyStew
Contributor V

Mark

Thanks for your input. I have not shown the initialization routine which initializes C1 to 0x90. So that is the default state, which I return C1 to after a delay.

In the code that I had working (6 months ago, same chip, same mask set, almost the same circuit board) and is almost identical to what I have now, I was writing 0xb0 to C1 for the start, then almost immediately writing the address to the data register.  But I tried what you suggested, though I'm not using interrupts, so I tried polling the Transmit Complete Flag. It never got set...just hung there at 0.

I'm not getting much in the way out output on the pins, though sometimes I get a short blip on SCL (which varies in duration depending on the the setting of the frequency register...an indication that the I2C peripheral is indeed generating that pulse). Also I was getting a periodic condition where both SDA and SCL went low and stayed there, about 4-10 seconds after startup. I tried putting code to reset the port every time I sent a message, to no avail.

Its behaving as if the peripheral is unstable, because the behaviour has a random factor to it. I'm wondering if I've changed something regarding the bus clock?

0 Kudos

2,564 Views
galadragos
Contributor III

Take care when you calculate the clock! Freescale engineers, in their infinite wisdom, made a very peculiar clock divider for the i2c.

The value written in the F[ICR] bits is not the actual division factor!

The clock is divided with some other value, which you must look it up in a table in the datasheet, as it can change depending on the part.

Here is a function that will automatically set the best values for the F register, thus giving a working clock.

<code>

void i2c_lld_bus_speed_calc(I2CDriver * i2cp, uint32_t bus_speed, uint32_t module_clk)

{

  I2C_F_MUL_Type mul=0;

  I2C_F_ICR_Type icr=0;

  uint32_t clk=0;

  uint32_t bclk=0;

  /* start with highest clock */

  i2cp->i2c->F=0x00;

  /* iterate through multiplier values */

  for (mul=0;mul<KINETIS_I2C_MAX_F_MUL_VALUE;mul+=1)

  {

  /* clock after the multiplier */

  clk=module_clk/(1<<mul);

  /* iterate through icr values */

  for(icr=0; icr<KINETIS_I2C_MAX_F_ICR_VALUE;icr+=1)

  {

  /* bus clock with current divisors */

  bclk=clk/kinetis_i2c_scl_divider[icr];

  /* reached target speed ? */

  if(bclk<=bus_speed)

  {

  /* set f register */

  i2cp->i2c->F = ( I2C_F_ICR(icr) | I2C_F_MULT(mul) );

  /* ready */

  return;

  }

  /* next icr value */

  }

  /* next mul value */

  }

  /*  Divider calculator will do a best effort calculation.

  *  If divisor value is too large, then set it to maximum allowed value;

  */

  i2cp->i2c->F = I2C_F_ICR_MASK|I2C_F_MULT_MASK;

}

</code>

And these are dependent on the chip type:

<code>

/* data types */

typedef uint16_t I2C_F_ICR_Type;

typedef uint8_t I2C_F_MUL_Type;

/* speed multiplier maximum value */

#define KINETIS_I2C_MAX_F_MUL_VALUE (I2C_F_MULT_MASK>>I2C_F_MULT_SHIFT)

/* scl divider maximum value */

#define KINETIS_I2C_MAX_F_ICR_VALUE (I2C_F_ICR_MASK+1)

/* look up table */

const I2C_F_ICR_Type kinetis_i2c_scl_divider[KINETIS_I2C_MAX_F_ICR_VALUE]=

{

  20,22,24,26,28,30,34,40,28,32,36,40,44,48,56,68,48,56,64,72,80,88,104,128,80,96,112,128,144,160,192,240,160,192,224,256,288,320,384,480,320,384,448,512,576,640,768,960,640,768,896,1024,1152,1280,1536,1920,1280,1536,1792,2048,2304,2560,3072,3840

};

</code>

0 Kudos

2,564 Views
DustyStew
Contributor V

Dragos

I was aware that the clock settings are according to a lookup table. I don't see how this should have any bearing on the behaviour of the peripheral. It should change the timing of the generated pulses on the I2C bus, nothing else. Perhaps this is not the case? Is there something undocumented here? What clock settings are you using?

0 Kudos

2,564 Views
mjbcswitzerland
Specialist V

Thomas

Below is the complete dual channel interrupt-driven I2C master driver from the uTasker project as reference. It show all initialisation, transmission initiatingand interrupt handling. It has been tested on many K and KL devices/boards (and is 95% compatible with Coldfire devices where the I2C controllers were derived from for the Kinetis parts). The I2C port initialisation also includes I2C bus dead-lock recovery (when a non-reset slave holds the I2C clock after the board is reset during an active transfer). The driver has been used in various industrial Kinetis projects during the last 3 years.

Regards

Mark

static __interrupt void _IIC_Interrupt_0(void)                       // I2C0 interrupt

{

I2C0_S = IIC_IIF;                                                // clear the interrupt flag
if (IIC_tx_control[0]->ucState & RX_ACTIVE) {
    unsigned char ucFirstRead = (I2C0_C1 & IIC_MTX);
    if (IIC_tx_control[0]->ucPresentLen == 1) {
        I2C0_C1 = (IIC_IEN | IIC_IIEN | IIC_MSTA | IIC_TXAK);    // we don't acknowledge last byte
    }
    else if (!IIC_tx_control[0]->ucPresentLen) {                 // we have completed the read
        I2C0_C1 = (IIC_IEN | IIC_TXAK);                          // send end condition and enable interrupts                                      //
        IIC_tx_control[0]->ucState &= ~(TX_WAIT | TX_ACTIVE | RX_ACTIVE);
        IIC_rx_control[0]->msgs++;
        if (IIC_rx_control[0]->wake_task) {                      // wake up the receiver task if desired
            uTaskerStateChange(IIC_rx_control[0]->wake_task, UTASKER_ACTIVATE); // wake up owner task
        }
    }
    else {
        I2C0_C1 = (IIC_IEN | IIC_IIEN | IIC_MSTA);               // ensure we acknowledge multibyte reads
    }

    if (ucFirstRead) {                                           // have we just sent the slave address?
        volatile unsigned char ucRx;
        ucRx = I2C0_D;                                           // dummy read
    }
    else {
        *IIC_rx_control[0]->IIC_queue.put++ = I2C0_D;            // read the byte
        IIC_rx_control[0]->IIC_queue.chars++;                    // and put it into the rx buffer
        if (IIC_rx_control[0]->IIC_queue.put >= IIC_rx_control[0]->IIC_queue.buffer_end) {
            IIC_rx_control[0]->IIC_queue.put = IIC_rx_control[0]->IIC_queue.QUEbuffer;
        }
    }

    if (IIC_tx_control[0]->ucPresentLen) {
        IIC_tx_control[0]->ucPresentLen--;
#if defined _WINDOWS
        I2C0_D = fnSimIIC_devices(IIC_RX_DATA, I2C0_D);          // simulate the interrupt directly
        I2C0_S |= IIC_IIF;
        iInts |= IIC_INT0;
#endif
    }
    else {                                                       // read sequence complete so continue with next write if something is waiting
        if (IIC_tx_control[0]->IIC_queue.chars != 0) {
            fnTxIIC(IIC_tx_control[0], 0);                       // we have another message to send so we can send a repeated start condition
        }
    }
    return;
}
else if (IIC_tx_control[0]->ucPresentLen--) {                    // TX_ACTIVE - send next byte to send if available 
    I2C0_D = *IIC_tx_control[0]->IIC_queue.get++;
    if (IIC_tx_control[0]->IIC_queue.get >= IIC_tx_control[0]->IIC_queue.buffer_end) {
        IIC_tx_control[0]->IIC_queue.get = IIC_tx_control[0]->IIC_queue.QUEbuffer; // handle the ring buffer
    }
#if defined _WINDOWS
    I2C0_S |= IIC_IIF;                                           // simulate the interrupt directly
    fnSimIIC_devices(IIC_TX_DATA, I2C0_D);
    iInts |= IIC_INT0;                                           // signal that an interrupt is to be generated
#endif
}
else {                                                           // last byte in TX_ACTIVE
    if (!(IIC_tx_control[0]->IIC_queue.chars)) {                 // transmission complete
        I2C0_C1 = (IIC_IEN | IIC_MTX);                           // send stop condition and disable interrupts
        IIC_tx_control[0]->ucState &= ~(TX_WAIT | TX_ACTIVE | RX_ACTIVE);
        if (IIC_tx_control[0]->wake_task) {
           uTaskerStateChange(IIC_tx_control[0]->wake_task, UTASKER_ACTIVATE); // wake up owner task since the transmission has terminated
        }
    }
    else {  
        fnTxIIC(IIC_tx_control[0], 0);                           // we have another message to send so we can send a repeated start condition
    }
}

}

#if NUMBER_IIC > 1
static __interrupt void _IIC_Interrupt_1(void)                       // I2C1 interrupt

{

I2C1_S = IIC_IIF;                                                // clear the interrupt flag
if (IIC_tx_control[1]->ucState & RX_ACTIVE) {
    unsigned char ucFirstRead = (I2C1_C1 & IIC_MTX);
    if (IIC_tx_control[1]->ucPresentLen == 1) {
        I2C1_C1 = (IIC_IEN | IIC_IIEN | IIC_MSTA | IIC_TXAK);    // we don't acknowledge last byte
    }
    else if (!IIC_tx_control[1]->ucPresentLen) {                 // we have completed the read
        I2C1_C1 = (IIC_IEN | IIC_TXAK);                          // send end condition and enable interrupts                                      //
        IIC_tx_control[1]->ucState &= ~(TX_WAIT | TX_ACTIVE | RX_ACTIVE);
        IIC_rx_control[1]->msgs++;
        if (IIC_rx_control[1]->wake_task ) {                     // wake up the receiver task if desired
           uTaskerStateChange(IIC_rx_control[1]->wake_task, UTASKER_ACTIVATE); // wake up owner task
        }
    }
    else {
        I2C1_C1 = (IIC_IEN | IIC_IIEN | IIC_MSTA);               // ensure we acknowledge multibyte reads
    }

    if (ucFirstRead) {                                           // have we just sent the slave address?
        volatile unsigned char ucRx;
        ucRx = I2C1_D;                                           // dummy read
    }
    else {
        *IIC_rx_control[1]->IIC_queue.put++ = I2C1_D;            // read the byte
        IIC_rx_control[1]->IIC_queue.chars++;                    // and put it into the rx buffer
        if (IIC_rx_control[1]->IIC_queue.put >= IIC_rx_control[1]->IIC_queue.buffer_end) {
            IIC_rx_control[1]->IIC_queue.put = IIC_rx_control[1]->IIC_queue.QUEbuffer;
        }
    }

    if (IIC_tx_control[1]->ucPresentLen) {
        IIC_tx_control[1]->ucPresentLen--;
    #if defined _WINDOWS
        I2C1_D = fnSimIIC_devices(IIC_RX_DATA, I2C1_D);          // simulate the interrupt directly
        I2C1_S |= IIC_IIF;
        iInts |= IIC_INT1;
    #endif
    }
    else {                                                       // read sequence complete so continue with next write if something is waiting
        if (IIC_tx_control[1]->IIC_queue.chars != 0) {
            fnTxIIC(IIC_tx_control[1], 1);                       // we have another message to send so we can send a repeated start condition
        }
    }
    return;
}
else if (IIC_tx_control[1]->ucPresentLen--) {                    // TX_ACTIVE - send next byte to send if available
    I2C1_D = *IIC_tx_control[1]->IIC_queue.get++;
    if (IIC_tx_control[1]->IIC_queue.get >= IIC_tx_control[1]->IIC_queue.buffer_end) {
        IIC_tx_control[1]->IIC_queue.get = IIC_tx_control[1]->IIC_queue.QUEbuffer; // handle the ring buffer
    }
    #if defined _WINDOWS
    I2C1_S |= IIC_IIF;                                           // simulate the interrupt directly
    fnSimIIC_devices(IIC_TX_DATA, I2C1_D);
    iInts |= IIC_INT1;                                           // signal that an interrupt is to be generated
    #endif
}
else {                                                           // last byte in TX_ACTIVE
    if (!(IIC_tx_control[1]->IIC_queue.chars)) {                 // transmission complete
        I2C1_C1 = (IIC_IEN | IIC_MTX);                           // send stop condition and disable interrupts

        IIC_tx_control[1]->ucState &= ~(TX_WAIT | TX_ACTIVE | RX_ACTIVE);

        if (IIC_tx_control[1]->wake_task ) {
           uTaskerStateChange(IIC_tx_control[1]->wake_task, UTASKER_ACTIVATE);// wake up owner task since the transmission has terminated
        }
    }
    else {
        fnTxIIC(IIC_tx_control[1], 1);                           // we have another message to send so we can send a repeated start condition
    }
}

}

#endif

// Initially the I2C pins were configured as inputs to allow an I2C bus lockup state to be detected

// - this routine checks for this state and generates clocks if needed to recover from it before setting I2C pin functions

//

static void fnConfigI2C_pins(QUEUE_HANDLE Channel)                   // {78}

{

if (Channel == 0)  {
#if ((defined KINETIS_KL02 || defined KINETIS_KL04 || defined KINETIS_KL05) && defined I2C0_A_0)
    while (_READ_PORT_MASK(A, PORTA_BIT4) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(A, PORTA_BIT3, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(A, PORTA_BIT3, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(A, 4,  (PA_4_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PA4 (alt. function 2)
    _CONFIG_PERIPHERAL(A, 3,  (PA_3_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PA3 (alt. function 2)
#elif ((defined KINETIS_KL04 || defined KINETIS_KL05) && defined I2C0_A_1)
    while (_READ_PORT_MASK(A, PORTA_BIT3) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(A, PORTA_BIT4, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(A, PORTA_BIT4, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(A, 3,  (PA_3_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PA3 (alt. function 3)
    _CONFIG_PERIPHERAL(A, 4,  (PA_4_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PA4 (alt. function 3)
#elif defined KINETIS_KL02 || defined KINETIS_KL04 || defined KINETIS_KL05
    while (_READ_PORT_MASK(B, PORTB_BIT4) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(B, PORTB_BIT3, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(B, PORTB_BIT3, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(B, 4,  (PB_4_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PB4 (alt. function 2)
    _CONFIG_PERIPHERAL(B, 3,  (PB_3_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PB3 (alt. function 2)
#elif defined I2C0_B_LOW
    while (_READ_PORT_MASK(B, PORTB_BIT1) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(B, PORTB_BIT0, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(B, PORTB_BIT0, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(B, 1,  (PB_1_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PB1 (alt. function 2)
    _CONFIG_PERIPHERAL(B, 0,  (PB_0_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PB0 (alt. function 2)
#elif defined I2C0_ON_D
    while (_READ_PORT_MASK(D, PORTD_BIT9) == 0) {                // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(D, PORTD_BIT8, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(D, PORTD_BIT8, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(D, 9,  (PD_9_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PD9 (alt. function 2)
    _CONFIG_PERIPHERAL(D, 8,  (PD_8_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PD8 (alt. function 2)
#elif (defined KINETIS_K64 || defined KINETIS_KL25 || defined KINETIS_KL26 || defined KINETIS_KL46) && defined I2C0_ON_E
    while (_READ_PORT_MASK(E, PORTE_BIT25) == 0) {               // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_HIGH(E, PORTE_BIT24, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_HIGH(E, PORTE_BIT24, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(E, 25, (PE_25_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PE25 (alt. function 5)
    _CONFIG_PERIPHERAL(E, 24, (PE_24_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PE24 (alt. function 5)
#elif defined KINETIS_K70 && defined I2C0_ON_E
    while (_READ_PORT_MASK(E, PORTE_BIT18) == 0) {               // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_HIGH(E, PORTE_BIT19, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_HIGH(E, PORTE_BIT19, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(E, 18, (PE_18_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PE18 (alt. function 4)
    _CONFIG_PERIPHERAL(E, 19, (PE_19_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PE19 (alt. function 4)
#else
    while (_READ_PORT_MASK(B, PORTB_BIT3) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(B, PORTB_BIT2, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(B, PORTB_BIT2, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(B, 3,  (PB_3_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PB3 (alt. function 2)
    _CONFIG_PERIPHERAL(B, 2,  (PB_2_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PB2 (alt. function 2)
#endif
}
#if CHIP_HAS_IIC > 1
else {
    #if defined KINETIS_KL02 && defined I2C1_A_1
    while (_READ_PORT_MASK(A, PORTA_BIT3) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(A, PORTA_BIT4, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(A, PORTA_BIT4, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(A, 3,  (PA_3_I2C1_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SDA on PA3 (alt. function 3)
    _CONFIG_PERIPHERAL(A, 4,  (PA_4_I2C1_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SCL on PA4 (alt. function 3)
    #elif defined KINETIS_KL02
    while (_READ_PORT_MASK(A, PORTA_BIT9) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(A, PORTA_BIT8, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(A, PORTA_BIT8, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(A, 9,  (PA_9_I2C1_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SDA on PA9 (alt. function 2)
    _CONFIG_PERIPHERAL(A, 8,  (PA_8_I2C1_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SCL on PA8 (alt. function 2)
    #elif defined I2C1_ON_E
    while (_READ_PORT_MASK(E, PORTE_BIT1) == 0) {                   // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(E, PORTE_BIT0, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(E, PORTE_BIT0, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(E, 0,  (PE_0_I2C1_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SDA on PE0 (alt. function 6)
    _CONFIG_PERIPHERAL(E, 1,  (PE_1_I2C1_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SCL on PE1 (alt. function 6)
    #else
    while (_READ_PORT_MASK(C, PORTC_BIT11) == 0) {                  // if the SDA line is low we clock the SCL line to free it
        _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_LOW(C, PORTC_BIT10, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'
        fnDelayLoop(10);
        _CONFIG_PORT_INPUT_FAST_LOW(C, PORTD_BIT10, (PORT_ODE | PORT_PS_UP_ENABLE));
        fnDelayLoop(10);
    }
    _CONFIG_PERIPHERAL(C, 11, (PC_11_I2C1_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SDA on PC11 (alt. function 2)
    _CONFIG_PERIPHERAL(C, 10, (PC_10_I2C1_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C1_SCL on PC10 (alt. function 2)
    #endif
}
#endif

}

// Configure the IIC hardware

//

extern void fnConfigIIC(IICTABLE *pars)

{

unsigned char ucSpeed;
if (pars->Channel == 0) {
    POWER_UP(4, SIM_SCGC4_I2C0);                                 // enable clock to module
#if ((defined KINETIS_KL04 || defined KINETIS_KL05) && (defined I2C0_A_0 || defined I2C0_A_1))
    _CONFIG_PORT_INPUT_FAST_LOW(A, (PORTA_BIT3 | PORTA_BIT4), (PORT_ODE | PORT_PS_UP_ENABLE));
#elif (defined KINETIS_KL02 && defined I2C0_A_0)
    _CONFIG_PORT_INPUT_FAST_LOW(A, (PORTA_BIT3 | PORTA_BIT4), (PORT_ODE | PORT_PS_UP_ENABLE));
#elif (defined KINETIS_KL02 || defined KINETIS_KL04 || defined KINETIS_KL05)
    _CONFIG_PORT_INPUT_FAST_LOW(B, (PORTB_BIT3 | PORTB_BIT4), (PORT_ODE | PORT_PS_UP_ENABLE));
#elif defined I2C0_B_LOW                                         // initially configure as input with pull-up
    _CONFIG_PORT_INPUT_FAST_LOW(B, (PORTB_BIT1 | PORTB_BIT0), (PORT_ODE | PORT_PS_UP_ENABLE));
#elif defined I2C0_ON_D
    _CONFIG_PORT_INPUT_FAST_LOW(D, (PORTD_BIT9 | PORTD_BIT8), (PORT_ODE | PORT_PS_UP_ENABLE));
#elif (defined KINETIS_K64 || defined KINETIS_KL25 || defined KINETIS_KL26 || defined KINETIS_KL46) && defined I2C0_ON_E
    _CONFIG_PORT_INPUT_FAST_HIGH(E, (PORTE_BIT25 | PORTE_BIT24), (PORT_ODE | PORT_PS_UP_ENABLE));
#elif defined KINETIS_K70 && defined I2C0_ON_E
    _CONFIG_PORT_INPUT_FAST_HIGH(E, (PORTE_BIT19 | PORTE_BIT18), (PORT_ODE | PORT_PS_UP_ENABLE));
#else
    _CONFIG_PORT_INPUT_FAST_LOW(B, (PORTB_BIT3 | PORTB_BIT2), (PORT_ODE | PORT_PS_UP_ENABLE));
#endif
}
else {                                                           // I2C channel 1
#if CHIP_HAS_IIC > 1
    POWER_UP(4, SIM_SCGC4_I2C1);                                 // enable clock to module
    #if defined KINETIS_KL02 && defined I2C1_A_1
    _CONFIG_PORT_INPUT_FAST_LOW(A, (PORTA_BIT3 | PORTA_BIT4), (PORT_ODE | PORT_PS_UP_ENABLE));
    #elif defined KINETIS_KL02
    _CONFIG_PORT_INPUT_FAST_LOW(A, (PORTA_BIT8 | PORTA_BIT9), (PORT_ODE | PORT_PS_UP_ENABLE));
    #elif defined I2C1_ON_E                                        // initially configure as input with pull-up
    _CONFIG_PORT_INPUT_FAST_LOW(E, (PORTE_BIT1 | PORTE_BIT0), (PORT_ODE | PORT_PS_UP_ENABLE));
    #else
    _CONFIG_PORT_INPUT_FAST_LOW(C, (PORTC_BIT11 | PORTC_BIT10), (PORT_ODE | PORT_PS_UP_ENABLE));
    #endif
#else
    return;
#endif
}

// The calculation of the correct divider ratio doesn't follow a formular so is best taken from a table.
// The required divider value is ((BUS_CLOCK/1000)/pars->usSpeed). Various typical speeds are supported here.
//                                                               {28}
// Note that some devices have a MULT field in the divider register which can be used as a prescaler - this is presently not used
// due to errate e6070 which doesn't allow a repeat start to be generated when it is set to a non-zero value
//
switch (pars->usSpeed) {
case 400:                                                        // high speed IIC
#if BUS_CLOCK > 60000000                                         // 75MHz
    ucSpeed = 0x1e;                                              // set about 400k with 75MHz bus frequency
#elif BUS_CLOCK > 50000000                                       // 60MHz
    ucSpeed = 0x20;                                              // set about 400k with 60MHz bus frequency
#elif BUS_CLOCK > 40000000                                       // 50MHz
    ucSpeed = 0x17;                                              // set about 400k with 50MHz bus frequency
#elif BUS_CLOCK > 30000000                                       // 40MHz
    ucSpeed = 0x16;                                              // set about 400k with 40MHz bus frequency
#elif BUS_CLOCK > 20000000                                       // 30MHz
    ucSpeed = 0x14;                                              // set about 400k with 30MHz bus frequency
#else                                                            // assume 20MHz
    ucSpeed = 0x11;                                              // set about 400k with 20MHz bus frequency
#endif
    break;
case 100:
default:                                                         // default to 100kHz
#if BUS_CLOCK > 60000000                                         // 75MHz
    ucSpeed = 0x2e;                                              // set about 100k with 75MHz bus frequency
#elif BUS_CLOCK > 50000000                                       // 60MHz
    ucSpeed = 0x2d;                                              // set about 100k with 60MHz bus frequency
#elif BUS_CLOCK > 40000000                                       // 50MHz
    ucSpeed = 0x2b;                                              // set about 100k with 50MHz bus frequency
#elif BUS_CLOCK > 30000000                                       // 40MHz
    ucSpeed = 0x2a;                                              // set about 100k with 40MHz bus frequency
#elif BUS_CLOCK > 20000000                                       // 30MHz
    ucSpeed = 0x28;                                              // set about 100k with 30MHz bus frequency
#else                                                            // assume 20MHz
    ucSpeed = 0x22;                                              // set about 100k with 20MHz bus frequency
#endif
    break;

case 50:
#if BUS_CLOCK > 60000000                                         // 75MHz
    ucSpeed = 0x36;                                              // set about 50k with 75MHz bus frequency
#elif BUS_CLOCK > 50000000                                       // 60MHz
    ucSpeed = 0x35;                                              // set about 50k with 60MHz bus frequency
#elif BUS_CLOCK > 40000000                                       // 50MHz
    ucSpeed = 0x33;                                              // set about 50k with 50MHz bus frequency
#elif BUS_CLOCK > 20000000                                       // 40MHz
    ucSpeed = 0x32;                                              // set about 50k with 40MHz bus frequency
#elif BUS_CLOCK > 10000000                                       // 30MHz
    ucSpeed = 0x2c;                                              // set about 50k with 30MHz bus frequency
#else                                                            // assume 20MHz
    ucSpeed = 0x29;                                              // set about 50k with 20MHz bus frequency
#endif
    break;
}
if (pars->Channel == 0) {
    I2C0_F = ucSpeed;
    fnEnterInterrupt(irq_I2C0_ID, PRIORITY_IIC0, _IIC_Interrupt_0);  // enter I2C0 interrupt handler
    I2C0_C1 = (IIC_IEN);                                         // enable IIC controller
}
#if CHIP_HAS_IIC > 1
else {
    I2C1_F = ucSpeed;
    fnEnterInterrupt(irq_I2C1_ID, PRIORITY_IIC1, _IIC_Interrupt_1);  // enter I2C1 interrupt handler
    I2C1_C1 = (IIC_IEN);                                         // enable IIC controller
}
#endif
#if defined _WINDOWS
fnConfigSimIIC(pars->Channel, (pars->usSpeed * 1000));
#endif

}

// Send a first byte to IIC bus

//

extern void fnTxIIC(IICQue *ptIICQue, QUEUE_HANDLE Channel)

{

static int iCheckTxIIC[NUMBER_IIC] = {0};                        // {78}
unsigned char ucAddress;
volatile unsigned char *register_ptr;
if (Channel == 0) {
    register_ptr = I2C0_C1_ADD;
}
else {
    register_ptr = I2C1_C1_ADD;
}

ptIICQue->ucPresentLen = *ptIICQue->IIC_queue.get++;             // get present length
if (ptIICQue->IIC_queue.get >= ptIICQue->IIC_queue.buffer_end) {
    ptIICQue->IIC_queue.get = ptIICQue->IIC_queue.QUEbuffer;     // handle circular buffer
}

if (ptIICQue->ucState & TX_ACTIVE) {                             // restart since we are hanging a second telegram on to previous one
    *register_ptr = (IIC_IEN | IIC_IIEN | IIC_MSTA | IIC_MTX | IIC_RSTA);
}
else {
    register_ptr++;                                              // move to status register
    if (iCheckTxIIC[Channel] == 0) {                             // {78} on first use we check that the bus is not held in a busy state (can happen when a reset took place during an acknowledge period and the slave is holding the bus)
        fnConfigI2C_pins(Channel);                               // check and configure pins for I2C use
        iCheckTxIIC[Channel] = 1;                                // checked only once
    }
    while (*register_ptr & IIC_IBB) {}                           // wait until the stop has actually been sent to avoid a further transmission being started in the mean time
    register_ptr--;                                              // move back to CR
    *register_ptr = (IIC_IEN | IIC_IIEN | IIC_MTX);              // set transmit mode with interrupt enabled
    *register_ptr = (IIC_IEN | IIC_IIEN | IIC_MSTA | IIC_MTX);   // set master mode to cause start condition to be sent
}
ucAddress = *ptIICQue->IIC_queue.get++;
*(register_ptr + 2) = ucAddress;                                 // send the slave address (this includes the rd/wr bit) - I2Cx_D
if (ptIICQue->IIC_queue.get >= ptIICQue->IIC_queue.buffer_end) {
    ptIICQue->IIC_queue.get = ptIICQue->IIC_queue.QUEbuffer;     // handle circular buffer
}

if (ucAddress & 0x01) {                                          // reading
    IIC_tx_control[Channel]->ucState |= (RX_ACTIVE | TX_ACTIVE);
    ptIICQue->IIC_queue.chars -= 3;
    IIC_rx_control[Channel]->wake_task = *ptIICQue->IIC_queue.get++; // enter task to be woken when reception has completed
    if (ptIICQue->IIC_queue.get >= ptIICQue->IIC_queue.buffer_end) {
        ptIICQue->IIC_queue.get = ptIICQue->IIC_queue.QUEbuffer; // handle circular buffer
    }
}
else {
    IIC_tx_control[Channel]->ucState |= (TX_ACTIVE);             // writing
    ptIICQue->IIC_queue.chars -= (ptIICQue->ucPresentLen+1);
}

#if defined _WINDOWS
if (Channel == 0) {
    I2C0_S |= IIC_IIF;                                           // simulate the interrupt directly
    fnSimIIC_devices(IIC_ADDRESS, I2C0_D);
    iInts |= IIC_INT0;                                           // signal that an interrupt is to be generated
}
    #if NUMBER_IIC > 1
else {
    I2C1_S |= IIC_IIF;                                           // simulate the interrupt directly
    fnSimIIC_devices(IIC_ADDRESS, I2C1_D);
    iInts |= IIC_INT1;                                           // signal that an interrupt is to be generated
}
    #endif
#endif

}

#endif

0 Kudos

2,565 Views
DustyStew
Contributor V

Sorry looks like the code that causes the error is this:

while(1)

{

     I2C0_S = I2C0_S; // clear error

     delay_ms();                   // delay a few milliseconds

     I2C0_C1 = 0xb0;  // try to generate a start

     delay_ms();                   // delay a few milliseconds

     I2C0_C1 = 0x90;  

     delay_sec();          // delay a second

}

That is, I can put a breakpoint in I2C0_C1 = 0xb0, and it will generate a loss of arbitration error when I execute that line of code.

0 Kudos

2,565 Views
DustyStew
Contributor V

BTW

No interrupts are occurring, scope shows SCL and SDA (on B4/B3 pins) high.

This is a 1N96 mask set KL05Z32

0 Kudos

2,565 Views
DustyStew
Contributor V

I've been stripping my code back to "nothing", that is, initialize system peripherals, initialize I2C, then here's main:

while(1)

{

     I2C0_S = I2C0_S; // clear error

     delay_ms();                   // delay a few milliseconds

     I2C0_C1 = 0xb0;  // try to generate a start

     delay_sec();          // delay a second

}

So MOST of the time, when writing the control register, status register shows loss of arbitration (ie when viewing it in CodeWarrior's register view). Nothing on the bus but pullups.

0 Kudos

2,565 Views
DustyStew
Contributor V

I have exactly the same problem I think. That is, I try to start a write cycle and the loss of arbitration bit goes active. Nothing else on the bus. SCL and SDA both high apparently. The funny thing is, I had this code running already, on a different board but same chip/maskset, and for some reason it stopped working. I made changes no doubt, but not to the I2C driver. At the moment I am theorizing some subtle timing problem I do not understand. But the documentation is useless, it does not provide insight into how the peripheral works under the hood. So I cannot think in terms of causes and effects, just randomingly trying stuff looking for some change in behaviour.

0 Kudos

2,565 Views
mjbcswitzerland
Specialist V

Hi

http://www.utasker.com/docs/uTasker/uTaskerIIC.PDF

The uTasker project includes interrupt driven I2C - tested on all K and KL processors.

http://www.utasker.com/forum/index.php?topic=1721.0

Regards

Mark

0 Kudos