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