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;
}
}
}
Hi everybody,
thanks to all your answers I've been able to fix the errors; there are still a few questions bothering me:
- about open-drain mode for KL05, I've searched for the option but the reference manual for KL05 regarding I2C reports: "When the package pins associated with IIC have their mux select configured for IIC operation, the pins (SCL and SDA) are driven in a pseudo open drain configuration."
In port control and interrupt summary, regarding open drain enable control, it says "NO" (not available) neither for PORTA nor for PORTB; a note about it writes:
drain when selected.
so I guess the pins are already correctly configured?
- I'd also like to ask if it's necessary to configure pull up (PE and PS bits in PORTx_PCRn) for SDA, since it's used as input when receiving;
- KL05 doesn't have double buffered I2C so I didn't have to add the fix (which is instead present in the driver written by Jan Rychter)
- I'm still confused as how to correctly 'write 1 to clear' the required bits like ARBL or IICIF; Jan Rychter driver and NXP bare metal driver use |= ; Mark Butcher uses a macro: "WRITE_ONE_TO_CLEAR(I2C0_S, I2C_IIF);"; the project I'm working on is developed using Code Warrior and I can't say if a similar macro has been provided
I'm going to add the resulting code to the answer as reference as soon as I finish cleaning the mess.
Thanks again to everybody for the answers!
"...Jan Rychter driver and NXP bare metal driver use |= ..."
Yes they do. Lots of that code out there.
At best it wastes CPU cycles and code space with unnecessary reads,
at worse it is dangerous depending on how the interrupt service routine is structured
from unintentionally clearing a status bit at the wrong time.
Marco
#define WRITE_ONE_TO_CLEAR(reg, flag) reg = (flag)
The macro makes the code clearer but is just writing a '1' to the defined bit(s).
The other sources using |= will work but they can also reset the arbitration lost bit (either intentionally or by error depending on the intention behind it and it should be commented to be able to know).
If one wants to clear other flags too could should expressly do it in code to avoid uncertainty:
WRITE_ONE_TO_CLEAR(I2C0_S, (I2C_IIF | I2C_IAL));
The macro method is also more efficient since it avoids a potentially superfluous read.
If you are doing anything more that a class-room demonstration or hobby work you also need to consider being able to remover from a blocked slave otherwise failure will require a power cycle to recover: https://community.nxp.com/thread/322977
Regards
Mark
[uTasker project developer for Kinetis and i.MX RT]
Thanx Mark,
That clears out any doubt about 'w1c' question.
I've also read the article you cited and found it useful; I agree that, when polling devices through serial communication, error recovery is fundamental to avoid leaving the device in a locked state.
Due to the simplicity of the current project, I'm leaving error management to the upper level; since periodic read are performed, if the last communication failed an error is signaled and the slave device is reset. I expect anyway to have to do more accurate error management when moving from the test board to the real device.
Also I think my actual code is useful as a minimal reference for driving I2C write, read and restart through interrupt, but it's left at its minimum for memory usage. At a later stage it's expected to add one more slave device, so the current code and error management will have to be updated accordingly (I still don't know if a message queue is necessary or not).
Here is my code (which is basically a stripped version of Jan Rychter code), please let me know if you find anything wrong.
Best regards,
Marco
i2c.h
#ifndef __I2C_DRIVER_H
#define __I2C_DRIVER_H
#include <stdint.h>
typedef uint8_t bool;
#define true 1
#define false 0
#define I2C_MAX_MSG_SIZE 10 // max size of writable data
// initialize i2c device
void i2c_init();
// write tx_data to I2C 'slave' device; if rx_data != 0, also read rx_length bytes to rx_data
// - tx_data are copied to internal buffer (at most I2C_MAX_MSG_SIZE bytes)
// - i2c_message() returns immediately; data read are available at end of communication
bool i2c_message(uint8_t slave, const uint8_t* tx_data, uint8_t tx_length, uint8_t* rx_data, uint8_t rx_length);
// I2C interrupt service routine
void isr_i2c();
// I2C peripheral working data model
typedef struct
{
uint8_t slave; // slave address used for current communication
uint8_t tx_length; // number of bytes to write
uint8_t rx_length; // number of bytes to read
uint8_t index; // index of current array (write/read)
uint8_t state; // current operation
uint8_t* rx_data; // pointer to read data (at least rx_length allocated bytes)
uint8_t tx_data[I2C_MAX_MSG_SIZE]; // buffer of data to write
} I2C_Channel;
// I2C_Channel.state values
#define I2C_AVAILABLE 0 // peripheral free
#define I2C_TX 1 // writing
#define I2C_RX_START 2 // start read
#define I2C_RX 3 // reading
#define I2C_ERROR 255 // error
// I2C peripheral working data
extern volatile I2C_Channel i2c_channel;
#endif
i2c.c:
#include "i2c.h"
#include "IO_Map.h" // KL05 register defines
// pins used:
// PIN: use ALT1 ALT2
// 9 SCL PTA3 I2C0_SCL
// 10 SDA PTA4 I2C0_SDA
volatile I2C_Channel i2c_channel;
// -----------------------------------------------------------------------------
// 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
// which one is correct ?
PORTA_PCR4 = (uint32_t) (PORT_PCR_MUX(0x02) | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK); // SDA
//PORTA_PCR4 = (uint32_t) (PORT_PCR_MUX(0x02)); // SDA
// enable interrupt for i2c
// this macro needs definition:
//set_irq_priority(8, 15); // set minimum priority
//enable_irq(INT_I2C0 - 16);
NVIC_ISER |= (1 << (INT_I2C0 - 16));
uint8_t mult = 0x00; // 0x00=1,0x01=2,0x10=4
uint8_t icr = 0x00; // 0x20;
I2C0_F = ((mult << I2C_F_MULT_SHIFT) | icr);
// enable I2C, enable interrupt, master mode
I2C0_C1 = (I2C_C1_IICEN_MASK);
I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK | I2C_C1_TX_MASK);
i2c_channel.state = I2C_AVAILABLE;
}
// -----------------------------------------------------------------------------
// i2c_message2
//
// -----------------------------------------------------------------------------
bool i2c_message(uint8_t slave, const uint8_t* tx_data, uint8_t tx_length, uint8_t* rx_data, uint8_t rx_length)
{
uint8_t i;
// - queue the message only if I2C is available
// - check against maximum tx_data size
if (i2c_channel.state != I2C_AVAILABLE ||
tx_length > I2C_MAX_MSG_SIZE)
{
return false;
}
// prepare working data
i2c_channel.slave = slave;
i2c_channel.tx_length = tx_length;
i2c_channel.rx_length = rx_length;
i2c_channel.index = 0;
i2c_channel.state = I2C_TX;
i2c_channel.rx_data = rx_data;
for (i = 0; i < tx_length; i++)
{
i2c_channel.tx_data[i] = tx_data[i];
}
// send I2C start
I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK | I2C_C1_MST_MASK | I2C_C1_TX_MASK);
// send first byte (slave address)
I2C0_D = slave;
// the rest of communication will be handled by ISR
return true;
}
// -----------------------------------------------------------------------------
// isr_i2c
//
// -----------------------------------------------------------------------------
void isr_i2c()
{
// check status register
uint8_t status = I2C0_S;
// clear interrupt service flag
I2C0_S = I2C_S_IICIF_MASK;
if (status & I2C_S_ARBL_MASK)
{
// lost arbitration (should not happen): stop i2c
I2C0_S = I2C_S_ARBL_MASK; // clear arbl (by setting it)
I2C0_C1 = 0x00;
i2c_channel.state = I2C_ERROR;
}
else
if (i2c_channel.state == I2C_TX)
{
if (status & I2C_S_RXAK_MASK)
{
// ACK not received
// Generate STOP (set MST=0), stay in tx mode, and disable further interrupts
I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_TX_MASK);
i2c_channel.state = I2C_ERROR;
}
else
if (i2c_channel.index < i2c_channel.tx_length)
{
// write byte
I2C0_D = i2c_channel.tx_data[i2c_channel.index];
i2c_channel.index++;
}
else
{
// end of tx: check if read must be performed
if (i2c_channel.rx_length > 0)
{
// Generate restart
I2C0_C1 |= (I2C_C1_RSTA_MASK | I2C_C1_TX_MASK);
// send slave address and read command
i2c_channel.state = I2C_RX_START;
I2C0_D = i2c_channel.slave | 0x01;
}
else
{
// Generate STOP (set MST=0), stay in tx mode, and disable further interrupts
I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_TX_MASK);
i2c_channel.state = I2C_AVAILABLE;
}
}
}
else
if (i2c_channel.state == I2C_RX_START)
{
// switch to reading
i2c_channel.state = I2C_RX;
i2c_channel.index = 0;
I2C0_C1 &= ~I2C_C1_TX_MASK; // switch to RX mode
if (i2c_channel.rx_length > 1)
{
I2C0_C1 &= ~I2C_C1_TXAK_MASK; // ack next byte read
}
else
{
I2C0_C1 |= I2C_C1_TXAK_MASK; // do not ACK the final read
}
// dummy read to start communication
*(i2c_channel.rx_data) = I2C0_D;
}
else
if (i2c_channel.state == I2C_RX)
{
if (i2c_channel.index == i2c_channel.rx_length - 1)
{
// 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
*(i2c_channel.rx_data + i2c_channel.index) = I2C0_D;
// Generate STOP (set MST=0), switch to RX mode, and disable further interrupts
I2C0_C1 = (I2C_C1_IICEN_MASK | I2C_C1_TX_MASK);
i2c_channel.state = I2C_AVAILABLE;
}
else
{
// read byte
*(i2c_channel.rx_data + i2c_channel.index) = I2C0_D;
i2c_channel.index++;
if (i2c_channel.index == i2c_channel.rx_length - 1)
{
I2C0_C1 |= I2C_C1_TXAK_MASK; // do not ACK the final read
}
else
{
I2C0_C1 &= ~I2C_C1_TXAK_MASK; // ack next byte read
}
}
}
}
Regarding the pull enable for SDA and SCL pins in your driver.
The correct muxing is the following
PORTA_PCR4 = (uint32_t) (PORT_PCR_MUX(0x02));
Since the pins are a pseudo open drain in this configuration, the will require only an external pull up resistor.
uint8_t icr = 0x20;
I2C0_F &= ~0xf;
I2C0_F |= ((mult << I2C_F_MULT_SHIFT) | icr);
Also check the erratas for your device.
Some device masks will not issue a Repeated Start if the upper nibble of F is non-zero.
ERRATA_1N96F_ /* Issue 6070: I2C: Repeated Start cannot be generated if the I2Cx_F[MULT] field is set to a non-zero value */
The code posted above appears to be based on the I2C flow chart in most of the Kinetis data sheets.
They neglect to explain when a Repeated Start needs issued on devices that both transmit and receive.
Such as writing the address to an EEPROM then reading the data from said address.
https://www.nxp.com/docs/en/user-guide/UM10204.pdf is the I2C specification document that explains the requirement.
RSTA is the bit that needs fiddled with to generate the required Repeated Start.
"I2C0_S |= I2C_S_ARBL_MASK; // clear arbl (by setting it)"
The flags are cleared by 'Writing a one to clear' (W1C).
Using '|=' can have unintentional side effects from the read in some processors.
An assignment (=) is sufficient to clear a flag in the status register for W1C flags.
I do not know the specifics of the KL05, this may not apply, it is not one of the devices I use.
Any device that has 'Double Buffered' I2C has a broken Repeated Start system.
A Repeated Start issued at the wrong time is what results in the arbitration error.
Mark describes the problem in Appendix A of this document:
https://www.utasker.com/docs/uTasker/uTasker_I2C.pdf
His code shows how to use the STARTF/STOPF interrupt to work around the problem.
Search the forms for issues related to the KL43/KL27 I2C.
The only official statement from NXP/Freescale about this issue is mentioned in an obscure migration guide:
https://www.nxp.com/docs/en/application-note/AN4997.pdf
Never in the data sheet or an errata. Their official position seems to be "Our IDE works".
Which it does by inserting undocumented random busy loops for undocumented duration to deal with the internal timing race caused by double buffering.
Hello,
Additionationally to the Mark suggestions.
You could check this bare-metal implementation for KL05 drivers including I2C.
https://cache.nxp.com/files/32bit/software/KL05-SC.zip
To find the peripheral drivers follow this path. KL05-SC\klxx-sc-baremetal\src\drivers
Since the KL05 is not supported by MCUXpresso SDK I recommend refer to KL03 or KL02 SDK drivers.
To download them, go to MCUxpresso SDK builder and click Select Development board. After logging in, please build the SDK for KL03 or KL02.
To find the SDK peripheral drivers follow this path \devices\MKL03Z4\drivers.
Best regards, Diego.
Hi Marco
I have extracted the part of code to send data via the KL05's I2C0 interface so that you can easily understand it:
Initialisation:
void i2c_init()
{
POWER_UP_ATOMIC(4, I2C0); // enable clock to module
fnEnterInterrupt(irq_I2C0_ID, PRIORITY_I2C0, _I2C_Interrupt_0); // enter I2C0 interrupt handler
_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)
I2C0_F = 0x28; // 100kHz
I2C0_C1 = (I2C_IEN); // enable I2C controller
I2C0_C1 = (I2C_IEN | I2C_IIEN | I2C_MTX); // set transmit mode with interrupt enabled
}
Here you can see that it is effectively the same as your code apart from the fact that you haven't configured the pins for open-drain mode; this may be a cause of difficulty.
Starting a transmission:
I2C0_D = slave_address;
I2C0_C1 = (I2C_IEN | I2C_IIEN | I2C_MSTA | I2C_MTX); // set master mode to cause start condition to be sent
Handling the Tx interrupt:
WRITE_ONE_TO_CLEAR(I2C0_S, I2C_IIF); // clear the interrupt flag (write '1' to clear)
if (more data to send) {
I2C0_D = next_byte; // send next byte
}
else {
I2C0_C1 = (I2C_IEN | I2C_MTX); // send stop condition and disable interrupts
}
That is all that is needed for master transmission.
Regards
Mark
[uTasker project developer for Kinetis and i.MX RT]