Greetings all,
I've been battling this for a while and not getting too far.
The problem is the KL05 receives the correct address from the master, ACKs it, then something very odd happens to the following byte. I have not been able to work out why.
A KL03 used to work perfectly in the KL05's place (using the KL05 now due to parts shortages).
The KL05 is running off the internal clock at 20Mhz, the bus clock div is 1 (also 20Mhz).
Attached is a image of the I2C signals. The address byte, and then the odd first data byte.
The pins and I2C peripheral are setup as following:
//i2c pin setup
PORTA->PCR[4] = PORT_PCR_MUX(0x02);
PORTA->PCR[3] = PORT_PCR_MUX(0x02);
//setup i2c hardware slave
CLOCK_EnableClock(kCLOCK_I2c0);
//set slave addr, no ranage
I2C0->A1 = 0x38 << 1U;
I2C0->RA = 0;
I2C0->C2 = 0;
//speed (I2C speed of master is ~315kHz)
I2C0->F = I2C_F_MULT(0) | I2C_F_ICR(6);
//glitch filter value, stop hold enable, and stop int
I2C0->FLT = I2C_FLT_FLT(0) | I2C_FLT_STOPF_MASK | I2C_FLT_STOPIE_MASK;
//reset all flags
I2C0->S = 0xFFU;
//enable slave, and slave int
I2C0->C1 = I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK;
//enable
NVIC_SetPriority(I2C0_IRQn, 1);
EnableIRQ(I2C0_IRQn);
The interrupt handler looks like:
void I2C0_IRQHandler(void)
{
//I2C IRQ handler
volatile uint8_t data = 0;
volatile uint8_t status;
bool doTransmit = false;
static bool isBusy = false;
//read flags
status = I2C_SlaveGetStatusFlags(I2C0);
//clear interrupt service flag
I2C0->S = I2C_S_IICIF_MASK;
//arbitration lost?
if (status & I2C_S_ARBL_MASK)
{
//clear ARBL
I2C0->S = I2C_S_ARBL_MASK;
//set receive mode
I2C0->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
//nothing more to do
return;
}
//check NAK
if (status & I2C_S_RXAK_MASK)
{
//set receive mode
I2C0->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
//read dummy
data = I2C0->D;
//do something?
}
else
//Check address match
if (status & I2C_S_IAAS_MASK)
{
//slave transmit, master reading from slave
isBusy = true;
if (status & I2C_S_SRW_MASK)
{
//change direction to send data
I2C0->C1 |= I2C_C1_TX_MASK;
doTransmit = true;
}
else
{
//slave receive, master writing to us
//send ack
I2C0->C1 &= ~(I2C_C1_TXAK_MASK);
//read dummy to release the bus
data = I2C0->D;
//reset inbuf
indatac = 0;
}
//do something here?
}
else
//check transfer complete flag
if (status & I2C_S_TCF_MASK)
{
if (status & I2C_S_SRW_MASK)
{
//slave transmit, master reading from slave
doTransmit = true;
}
else
{
//slave receive, master writing to slave
//ack
I2C0->C1 &= ~I2C_C1_TXAK_MASK;
//get data
data = I2C0->D;
indata[indatac++] = data;
}
}
else
{
//read dummy to release bus
data = I2C0->D;
}
//send data if there is the need
if (doTransmit)
{
//send data
if (!sendComplete)
{
I2C0->D = outdata[outdatac++];
}
else
{
//switch to receive mode
I2C0->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
//read dummy to release bus
data = I2C0->D;
}
}
//done
}
Anyone have any ideas what's going on here?
Thanks in advance.
已解决! 转到解答。
Hi
I am surprised that core speed has an effect - are you sure there are not race states that are avoided when the processor is running slightly faster?
When I get some time I may try testing the slowest core and bus clocks for reliable operation at a certain I2C master rate to see whether there is something specifically limiting it. I do have a KL02 slave (same I2C controller as in KL05) in operation at the moment that is clocked at 20.97MHz core and 10.485MHz bus/flash which hasn't shown any problems. The same application running on a KL03 (similar issue as you in that I need to use both chips due to shortages, but both are footprint compatible) at 8MHz core and 4MHz bus/flash behaves equivalently.
In both cases the I2C master is using a 50kHz I2C clock.
Regards
Mark
I'm surprised too.
I'm quite certain there is nothing else going on that an increase in clock rate would change... the firmware just isn't that complicated or large.
In this case the master I2C clock is approx 350khz.
Thanks again for your help.
Hi
See appendix A of https://www.utasker.com/docs/uTasker/uTasker_I2C.pdf for details of behavioral differences of the I2C controllers in those two parts.
Regards
Mark
Thanks for your reply, but I'm not sure how it helps me?
The KL05 that I'm having trouble with does not have double-buffered I2C.
It also does not have a start condition interrupt event/flag.
Hi
But your correctly working KL03 does have double-buffered I2C so you need to see how the two types differ in order to possibly detected what your problem is in the KL05 case.
Regards
Mark
Mark, yes the KL03 does.
I gave up attempting to use the KL03 code (old FSL libs) some time ago.
I'm now handling the KL05's interrupt handling myself (as in the above code).
I cant work out why the KL05 isnt ACK'ing or triggering an interrupt after the first byte after the address is sent. After the address is handled correctly (as far as i can tell), C1/S are correctly set.
Hi
This is the basic slave driver from the uTasker project (when running on a non-double buffered KL part) - maybe you can find something that explains your difficulty (I just show handling a master write)
Regards
Mark
Initialisation code:
POWER_UP_ATOMIC(4, I2C0); // enable clock to module
fnEnterInterrupt(irq_I2C0_ID, PRIORITY_I2C0, _I2C_Interrupt_0); // enter I2C0 interrupt handler
ptrI2C->I2C_A1 = pars->ucSlaveAddress; // program the slave's address
_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)
ptrI2C->I2C_F = 0; // slave doesn't have a specific speed programmed
ptrI2C->I2C_C1 = (I2C_IEN); // enable I2C controller
ptrI2C->I2C_C1 |= I2C_IIEN; // immediately enable interrupts if operating as a slave
ptrI2C->I2C_FLT = I2C_FLT_FLT_STOPIE; // enable stop condition interrupt
Interrupt handling code:
if ((ptrI2C->I2C_FLT & I2C_FLT_FLT_INT) != 0) { // if the slave has enabled start/stop condition interrupt(s)
if ((ptrI2C->I2C_FLT & I2C_FLT_FLT_STOPF) != 0) { // if a stop condition has been detected
WRITE_ONE_TO_CLEAR(ptrI2C->I2C_FLT, I2C_FLT_FLT_STOPF); // clear the stop flag (write '1' to clear) [note that if the start flag is also set, it is also cleared by this action]
WRITE_ONE_TO_CLEAR(ptrI2C->I2C_S, I2C_IIF); // clear the interrupt flag (write '1' to clear)
if ((ptrI2C->I2C_S & (I2C_IAAS | I2C_TCF)) != (I2C_IAAS | I2C_TCF)) { // if the slave is already addressed we allow the reception handling to continue since we will have cleared the reception interrupt
return;
}
}
}
WRITE_ONE_TO_CLEAR_INTERRUPT(ptrI2C->I2C_S, I2C_IIF, irq_I2C0_ID); // clear the interrupt flag (write '1' to clear)
if (ptrI2C->I2C_F == 0) { // if we are slave
if ((ptrI2C->I2C_S & I2C_IAAS) != 0) { // if being addressed as a slave
if ((ptrI2C->I2C_S & I2C_SRW) != 0) { // if the master is addressing for a read
....
}
else { // addressed for write
unsigned char ucAddress;
ptrI2C->I2C_C1 = (I2C_IEN | I2C_IIEN); // write to C1 in order to clear the IAAS flag - remain in receive mode (a write to I2C_C1 clears IAAS)
ucAddress = ptrI2C->I2C_D; // dummy read (this reads out address as addressed)
}
}
else { // data being received from master
unsigned char ucRxData = ptrI2C->I2C_D; // read data byte
return;
}
}
Mark,
Thanks a lot for providing the snippet Mark.
Unfortunately implementing a very similar i2c handling int changes nothing. I still end up with the same lack of int/ack after the sent data byte.
//i2c pin setup
BOARD_INITPINS_EVE_CTP_SDA_PORT->PCR[BOARD_INITPINS_EVE_CTP_SDA_PIN] = PORT_PCR_MUX(0x02);
BOARD_INITPINS_EVE_CTP_SCL_PORT->PCR[BOARD_INITPINS_EVE_CTP_SCL_PIN] = PORT_PCR_MUX(0x02);
//i2c NVIC setup
NVIC_SetPriority(I2C0_IRQn, 1); //set i2c0 int priority
EnableIRQ(I2C0_IRQn); //enable int
//setup i2c hardware slave
CLOCK_EnableClock(kCLOCK_I2c0); //enable i2c clock
I2C0->A1 = 0x38 << 1U; //set our slave address
I2C0->RA = 0; //no addr range
I2C0->C2 = 0;
I2C0->SMB = 0; //no smbus use
//speed (I2C speed of master is ~315kHz)
I2C0->F = 0; //set speed
I2C0->S = 0xFFU; //reset all flags
I2C0->C1 = I2C_C1_IICEN_MASK; //enable i2c peripheral
I2C0->C1 |= I2C_C1_IICIE_MASK; //enable ints
I2C0->FLT = I2C_FLT_STOPIE_MASK; //enable stop cond int
int:
void I2C0_IRQHandler(void)
{
//I2C IRQ handler
volatile uint8_t data = 0;
volatile uint8_t status;
bool doTransmit = false;
//arbitration lost?
if (I2C0->S & I2C_S_ARBL_MASK)
{
//clear ARBL
I2C0->S = I2C_S_ARBL_MASK;
ARBLostCount++;
}
//check for stop flag
if (I2C0->FLT & I2C_FLT_STOPF_MASK)
{
//stop condition has been detected
I2C0->FLT = I2C_FLT_STOPF_MASK; //clear flag
I2C0->S = I2C_S_IICIF_MASK; //clear interrupt service flag
if ((I2C0->S & (I2C_S_IAAS_MASK | I2C_S_TCF_MASK)) != (I2C_S_IAAS_MASK | I2C_S_TCF_MASK))
{
//if the slave is already addressed we allow the reception handling to continue since we will have cleared the reception interrupt
return;
}
}
//clear interrupt service flag
I2C0->S = I2C_S_IICIF_MASK;
//check address match
if (I2C0->S & I2C_S_IAAS_MASK)
{
//address matched
isBusy = true;
if (I2C0->S & I2C_S_SRW_MASK)
{
//slave transmit, master reading from us
//change direction to send data
I2C0->C1 |= I2C_C1_TX_MASK;
//clear interrupt service flag
I2C0->S = I2C_S_IICIF_MASK;
//flag transmit
doTransmit = true;
}
else
{
//slave receive, master writing to us
//send ack, recv mode
//I2C0->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
I2C0->C1 = I2C_C1_IICEN_MASK | I2C_C1_IICIE_MASK;
//read dummy to release the bus
data = I2C0->D;
//reset inbuf count
indatac = 0;
}
}
else
{
//check transfer complete flag
if (I2C0->S & I2C_S_TCF_MASK)
{
if (I2C0->S & I2C_S_SRW_MASK)
{
//slave transmit, master reading from slave
doTransmit = true;
}
else
{
//slave receive, master writing to slave
//clear interrupt service flag
I2C0->S = I2C_S_IICIF_MASK;
//ack
I2C0->C1 &= ~I2C_C1_TXAK_MASK;
//get data
data = I2C0->D;
indata[indatac++] = data;
}
}
else
{
//clear interrupt service flag
I2C0->S = I2C_S_IICIF_MASK;
//read dummy to release bus
data = I2C0->D;
}
}
//send data if there is the need
if (doTransmit)
{
//send data
}
//done
(void)data;
}
At the location of the B cursor, the INT is called with IAAS and !SRW (as it should).
The INT is never triggered again after that. Nothing ever happens after the B cursor.
I've found something else interesting...
I have a variation of the fw that using blocking (non INT driven) handling of I2C0.
With I2C0->F=0, it does not work. It gives the same immediate result as the INT handling code.
With I2C0->F=69 (MULT=2 ICR=5), it does work for 4-5 I2C transactions before failing.
This is a capture of the blocking i2c handling code, with F=69.
Note the very short SDA pulses which i have circled in red (i have no idea what they are or are caused by).