AnsweredAssumed Answered

Re: I²C malfunction/freezing?

Question asked by lpcware Employee on Jun 15, 2016
Latest reply on Jun 15, 2016 by lpcware
Content originally posted in LPCWare by Helmut Stettmaier on Sun Mar 30 04:32:19 MST 2014
Hello,
there is a strange and very confusing/frustrating problem with the LPC812 I²C bus. I kindly ask for help in avoiding, bypassing or recovering from such problems:

I connect an MS5611 as the only I²C-slave to the LPC812 and I use PIO0_13 as SCL and PIO0_12 as SDA, the distances between the chips are <3cm in my hardware and about 5cm in the LPCXpresso prototype boards, I use external 2k2 resistors.
The I²C-clock is about 400 kHz - earlier it was about 90 kHz as long as I followed the advices in the user manual, table 192: 12 MHz Xtal, 24 MHz clock, LPC_I2C->DIV= 15, now I use LPC_I2C->DIV= 3 and you can see the I²C clock in the sniffer output. But clocking is not the current problem.

The transactions consist of 2-byte command sequences (address and 1 byte command) and of sequences over 2 or 3 bytes to be read (address and 2 or 3 bytes). Several commands and data reads are executed one immediately after the other as a sequence and then a pause of about 10 ms ist inserted. A typical sequence is:
START <address write (EE)> <command read ADC (00)> STOP followed immediately by
START <address read (EF)> <read byte 1> <read byte 2> <read byte 3> NAK & STOP followed immediately by
START <address write (EE)> <command measure temperature> STOP and then about 10 ms pause for the MS5611 to do what being asked for.

After RESET and start this works fine for a while (some times 20 seconds, other times up to 5 minutes or even longer after the clock was changed to 400 kHz) and then the I²C-Bus comes out of order and is stuck.

The attachment contains a logic sniffer display of a wrong going transaction sketched above:
It shows the START <address write> <command read ADC> STOP START <address read> and during this the fault occurs:
The red trace (bottom) going low marks the beginning of my error routine which is called because of a I2C.STAT.MSTSTATE=4, meaning that the <address read> wasn't ACKed by any slave - and this BEFORE the 9th clock for the ACK was sent. The state was saved in the error data structure and read with the debugger. Of course you can see, that one of the clock-low-states during the address was longer than the others, obviously twice as long. We all think that a clock pulse was lost but was counted by the hardware.
The purple trace, input at UART0 (PIO0_4 for both, TxD and RxD), had made me suspect that an interferring interrupt request could be a reason. In most (all but one) observed cases there was something incoming or outgoing to/from UART0/PIO0_4 when the problem arose; only one case was observed with "quiet" UART0, but in this case a Systick-Interrupt could have been occurred but I have no trace of this.
These are the peripheral interrupts which I use so far: SysTick, UART, MRT and I²C.

There are also other, much more complicated erranous sequences which I could not catch fully with my limited logic sniffer: START/STOP sent twice or other incorrect procedure steps. In all cases the the bus freezes.

The problem arises with different hardwares: 2 LPCXpressos and one of my own hardware with a 16-pin device. The chips are of version "M285.102 nB3424C" (I guess it is Rev. 4C, I replaced the old chips in my Rev.-A-LPCXpressos by this new chip).

I tried to write a recovery procedure, which shall be called when a code in STAT.MSTSTATE is not as expected. It mainly logs the event, tries to send an extra stop condition and even stops and resets the I²C peripheral (LPC_SYSCON->PRESETCTRL&= ~(I2C_PRESETCTRL_Bits); LPC_SYSCON->SYSAHBCLKCTRL&= ~ (I2C_SYSAHBCLKCTRL_Bits);), but nothing helps.
There were different such attempts, but the result was allways the same: After such a fault the data line remains static (low or high) and there isn't sent anythong else on the clock line (stays high) until, at least, reset through the debugger or even power off.

The first question is: What could I have done wrong that all this happens?.
The second question is: Is there any hint how to avoid or bypass this problem?
Third question: If the problem cannot be avoided or bypassed - how do I recover from it? My attempts so far failed.
Is timeout the given way to recover from such a fault? How would this to be done? How do I reliably sense the fault and how do I reliably restart the I²C peripheral?

This is the source of the I²C- and logical MS5611- driver:

/** \file I2Csensor.c
\brief Treiberschicht für das kDevice812: I²C-Anbindung und LowLevel für MS5611.
\author Helmut Stettmaier (stm), helmut@stettmaier.de
\version 1.0
\date 15. Februar 2014
\copyright Es wurde Texte und Code-Muster aus dem LPC812-Manual übernommen.
*/

#include "LPC8xx.h"// Registernamen
#include "LPC8xxB.h"// weitere Definitionen (nicht CMSIS) für Bit-Namen
#include "hal.h"
#include "I2Csensor.h"
#include "main.h"

void I2CCtrlResetEngine();
void I2CCtrlSendAddressWrite();
void I2CCtrlSendAddressRead();
void I2CCtrlSendResetCmd();
void I2CCtrlStartTemp();
void I2CCtrlStartPressure();
void I2CCtrlSendCalReadCmd();
void I2CCtrlReadADCCommand();
void I2CCtrlStop();
void I2CCtrlSequStop();
void I2CCtrlResetHardware(void);
void I2CCtrlReadByte1();
void I2CCtrlReadByte2();
void I2CCtrlCalReadByte2();
void I2CCtrlTempReadByte3();
void I2CCtrlPressureReadByte3();

/* Note for LPC Forum readers: I²C is set up in the HAL as follows:
LPC_SYSCON->SYSAHBCLKCTRL |= (
SYSCON_SYSAHBCLKCTRL_Bits | UART_SYSAHBCLKCTRL_Bits |
GPIO_SYSAHBCLKCTRL_Bits | I2C_SYSAHBCLKCTRL_Bits |
SCT_SYSAHBCLKCTRL_Bits); // SCT temporär für den Test-Ohrhöhrer
LPC_SYSCON->PRESETCTRL= (
SYSCON_PRESETCTRL_Bits | UART_PRESETCTRL_Bits |
GPIO_PRESETCTRL_Bits | I2C_PRESETCTRL_Bits |
SCT_PRESETCTRL_Bits); // SCT temporär für den Test-Ohrhöhrer
...
LPC_I2C->DIV= 3; // 14;
LPC_I2C->CFG= 1;
*/

/** Die I²C-Adresse des Sensors (CS=low) */
#define MS5611_I2C_ADR 0xee

/** Die 6 Kalibrierungswerte des MS5611 ohne CRC. */
uint16_t C[7];

/** Namen: C[0] ist C1 u.s.w. */
#define C1 C[1]
#define C2 C[2]
#define C3 C[3]
#define C4 C[4]
#define C5 C[5]
#define C6 C[6]
/** Zähler für die Kalibrierwerte-Schleife im I²C-Automaten. */
#define cnt C[0]

/** Zwischengröße: Differenz zur "Referenztemperatur" in D[2].
Wird zur Kompensation des Temperatur-Fehlers im Druckwert benötigt. */
uint32_t dT;

/** Export: Temperatur-kompensierter Druckwert in Pa. */
uint32_t rawPressure;

/** . */
uint32_t input;

/** Status des I²C-Automaten. */
uint8_t MS5611state;

/** . */
#define MS5611statePanicFlag 0x80

/** Kopie von LPC_I2C->STAT. */
uint8_t cpyI2CSTAT;

/** Steuer-Bit für I²C: Master - Starte Senden. */
#define MSTSTART 0x02
/** Steuer-Bit für I²C: Master - Setze Kommunikation fort. */
#define MSTCONTINUE 0x01
/** Steuer-Bit für I²C: Master - Beende Kommunikation. */
#define MSTSTOP 0x04

/** Besonderer Zustand der I2C-Engine: Setzt die Maschinerie zurück. */
#define I2CCtrlReset0
/** Besonderer Zustand der I2C-Engine: Schleifen-Beginn für die Kal.-Konstanten. */
#define I2CCtrlCalLoop4
/** Besonderer Zustand der I2C-Engine: Startet neu (ohne Rücksetzen). */
#define I2CCtrlRestart10
/** Besonderer Zustand der I2C-Engine: Endlos-Schleife für Temperatur und Druck. */
#define I2CCtrlLoop10
/** Größte erlaubte Zustands-Zahl für Bereichsmessung. */
#define I2CCtrlUppLimit29

void (*I2CCtrlTable[])() = {
I2CCtrlResetEngine,//  0
/* MS5611-Reset-Kommando */
I2CCtrlSendAddressWrite,//  1 Adresse (Write)MSTSTART
I2CCtrlSendResetCmd,//  2 Kommando-ByteMSTCONTINUE
I2CCtrlSequStop,//  3 MSTSTOP, INTENCLR
/* 6mal Kalibrier-Konstante einlesen (C[0]..C[5]) */
I2CCtrlSendAddressWrite,//  4 Adresse (Write)MSTSTART
I2CCtrlSendCalReadCmd,//  5 Kommando-ByteMSTCONTINUE
I2CCtrlStop,//  6 MSTSTOP
I2CCtrlSendAddressRead,//  7 Adresse (Read)MSTSTART
I2CCtrlReadByte1,//  8 lies 1. ByteMSTCONTINUE
I2CCtrlCalReadByte2,//  9 lies 2. ByteMSTSTOP, INTENCLR
/* zurück zu 4 (I2CCtrlCalLoop) falls noch Kalibrierwerte zu lesen sind */
/* Temperatur-Messung einleiten */
I2CCtrlSendAddressWrite,// 10 Adresse (Write)MSTSTART
I2CCtrlStartTemp,// 11 Kommando-ByteMSTCONTINUE
I2CCtrlSequStop,// 12 MSTSTOP, INTENCLR
/* Temperatur-Wert einlesen und kalibrieren */
I2CCtrlSendAddressWrite,// 13 Adresse (Write)MSTSTART
I2CCtrlReadADCCommand,// 14 Kommando-ByteMSTCONTINUE
I2CCtrlStop,// 15 MSTSTOP
I2CCtrlSendAddressRead,// 16 Adresse (Read)MSTSTART
I2CCtrlReadByte1,// 17 lies 1. ByteMSTCONTINUE
I2CCtrlReadByte2,// 18 lies 2. ByteMSTCONTINUE
I2CCtrlTempReadByte3,// 19 lies 3. ByteMSTSTOP, INTENCLR
/* Druck-Messung einleiten */
I2CCtrlSendAddressWrite,// 20 Adresse (Write)MSTSTART
I2CCtrlStartPressure,// 21 Kommando-ByteMSTCONTINUE
I2CCtrlSequStop,// 22 MSTSTOP, INTENCLR
/* Druck-Wert einlesen und kalibrieren */
I2CCtrlSendAddressWrite,// 23 Adresse (Write)MSTSTART
I2CCtrlReadADCCommand,// 24 Kommando-ByteMSTCONTINUE
I2CCtrlStop,// 25 MSTSTOP
I2CCtrlSendAddressRead,// 26 Adresse (Read)MSTSTART
I2CCtrlReadByte1,// 27 lies 1. ByteMSTCONTINUE
I2CCtrlReadByte2,// 28 lies 2. ByteMSTCONTINUE
I2CCtrlPressureReadByte3// 29 lies 3. ByteMSTSTOP, INTENCLR
/* zurück zu 10 (I2CCtrlLoop) */
};

/** Fehlerroutine: Mitten in der Sequenz (bei unbekanntem Zustand des I²C-Gerätes)
abbrechen und die I²C-Engine auf "Neustart" setzen, Eintrag in Fehlerspeicher
machen. */
void I2CCtrlAbortSequence(void) {
debugRedLow;//<<<<<<<<<<<<<<<<<
int i= (xErrRecord++) & 0x03;
ErrRecord.tag= ErrRecordI2C;
ErrRecord.subtag= MS5611state;
ErrRecord.SysTickStamp= (uint16_t)(SysTickCounter) & 0xffff;
ErrRecord.v.I2C.cpyI2CSTAT= cpyI2CSTAT;
ErrRecord.v.I2C.currI2Cdata= input;
setLED_BlinkPattern(LED_Blink_2Blinks);
LPC_I2C->INTENCLR= 1;
LPC_I2C->MSTCTL= MSTSTOP;
debugOrangeLow;//<<<<<<<<<<<<<<<<
cnt= 6; MS5611state= (I2CCtrlRestart-1) | MS5611statePanicFlag;
debugRedHigh;//<<<<<<<<<<<<<<<
}

/** Sende die MS5611-Adresse (als Beginn einer I²C-Kommandosequenz). */
void I2CCtrlSendAddressWrite(void) {
LPC_SYSCON->SYSAHBCLKCTRL|= (I2C_SYSAHBCLKCTRL_Bits);
LPC_SYSCON->PRESETCTRL|= (I2C_PRESETCTRL_Bits);
debugOrangeHigh;//<<<<<<<<<<
LPC_I2C->MSTDAT= MS5611_I2C_ADR;
LPC_I2C->MSTCTL= MSTSTART;
LPC_I2C->INTENSET= 1;
input= 0;
};

/** Beende eine I²C-Kommandosequenz. */
void I2CCtrlSequStop(void) {
LPC_I2C->MSTCTL= MSTSTOP;
LPC_I2C->INTENCLR= 1; // löst nicht mehr aus.
}

/** Reset I²C Hardware. Panik! */
void I2CCtrlResetHardware(void) {
debugOrangeHigh;//<<<<<<<<<<<
LPC_SYSCON->PRESETCTRL&= ~(I2C_PRESETCTRL_Bits);
LPC_SYSCON->SYSAHBCLKCTRL&= ~ (I2C_SYSAHBCLKCTRL_Bits);
MS5611state&= ~MS5611statePanicFlag;
debugOrangeLow;//<<<<<<<<<<
}

/** Setze den I²C-Automaten zurück. */
void I2CCtrlResetEngine(void) {
cnt= 6; MS5611state= 0;
}

/** Sende die MS5611-Adresse zum Lesen. */
void I2CCtrlSendAddressRead(void) {
if (cpyI2CSTAT==1 /* ist I²C idle? */) {
LPC_I2C->MSTDAT= MS5611_I2C_ADR | 1;
LPC_I2C->MSTCTL= MSTSTART;
} else I2CCtrlAbortSequence();
};

/** Sende den Reset-Befehl als Datenbyte. */
void I2CCtrlSendResetCmd(void) {
//if (cpyI2CSTAT==5 /* Transmit_ready? */) {
LPC_I2C->MSTDAT= 0x1e;
LPC_I2C->MSTCTL= MSTCONTINUE;
//} else I2CCtrlAbortSequence();
}

/** Sende das Initialize-Temperature-Measurement-Kommando als Datenbyte. */
void I2CCtrlStartTemp(void) {
LPC_I2C->MSTDAT= 0x58;
LPC_I2C->MSTCTL= MSTCONTINUE;
}

/** Sende das Initialize-Pressure-Measurement-Kommando als Datenbyte. */
void I2CCtrlStartPressure(void) {
LPC_I2C->MSTDAT= 0x48;
LPC_I2C->MSTCTL= MSTCONTINUE;
}

/** Sende das CalConst-Lese-Kommando als Datenbyte. cnt ist # der CalConst. */
void I2CCtrlSendCalReadCmd(void) {
LPC_I2C->MSTDAT= 0xa0 | (cnt<<1);
LPC_I2C->MSTCTL= MSTCONTINUE;
}

/** . */
void I2CCtrlReadADCCommand(void) {
//if (cpyI2CSTAT==5 /* Transmit_ready? */) {
LPC_I2C->MSTDAT= 0x00;
LPC_I2C->MSTCTL= MSTCONTINUE;
//} else I2CCtrlAbortSequence();
}

/** Sende Stop-Condition, lasse den Interrupt aber eingeschaltet. */
void I2CCtrlStop(void) {
LPC_I2C->MSTCTL= MSTSTOP; // sendet STOP und löst sofort mit IDLE wieder aus
}

/** Lesen: 1. Byte. */
void I2CCtrlReadByte1(void) {
if (cpyI2CSTAT==3 /* Read_ready? */) {
input= LPC_I2C->MSTDAT;
LPC_I2C->MSTCTL= MSTCONTINUE;
} else I2CCtrlAbortSequence();
}

/** Lesen: 2. Byte (neutral). */
void I2CCtrlReadByte2(void) {
if (cpyI2CSTAT==3 /* Read_ready? */) {
input= input<<8 | LPC_I2C->MSTDAT;
LPC_I2C->MSTCTL= MSTCONTINUE;
} else I2CCtrlAbortSequence();
}

/** Kalibrier-Konstante lesen: 2. Byte, zusammensetzen & abspeichern.
cnt ist # der CalConst. */
void I2CCtrlCalReadByte2(void) {
if (cpyI2CSTAT==3 /* Read_ready? */) {
C[cnt]= input<<8 | LPC_I2C->MSTDAT;
/* Es müssen 6 Konstanten eingelesen werden: C[6]..C[1]. Dies wird mit
cnt (von 0 bis 5) gezählt. Wenn cnt also noch <6 ist muss dieser
Lesevorgang wiederholt werden; dazu wird (ugly) einfach MS5611state
zurück gestellt. */
if (--cnt) MS5611state= I2CCtrlCalLoop-1;
/* Sende Stop-Condition und beende die Sequenz. */
LPC_I2C->MSTCTL= MSTSTOP;
LPC_I2C->INTENCLR= 1;
} else I2CCtrlAbortSequence();
}

/** Lies das 3. Byte des Temperatur-Wertes ein, Berechne dT. */
void I2CCtrlTempReadByte3(void) {
uint32_t D2= input<<8 | LPC_I2C->MSTDAT;
dT = D2 - (((int32_t)(C5)) << 8);
LPC_I2C->MSTCTL= MSTSTOP;
}

/** Lies das 3. Byte des Druck-Wertes ein, Berechne dT. */
void I2CCtrlPressureReadByte3(void) {
int64_t s, o;
uint32_t D1= input<<8 | LPC_I2C->MSTDAT;
o =(((int64_t)(C2))<<16)+ (((C4)* (int64_t)dT)>>7);
s =(((int64_t)(C1))<<15)+ (((C3)* (int64_t)dT)>>8);
rawPressure =((((int64_t)D1 *s)>>21) -o)>>15;
LPC_I2C->MSTCTL= MSTSTOP;
mainStatus|= mstatMS5611PrValread;
MS5611state= I2CCtrlLoop-1;
}

/** Start des MS5611-Abfrage-Automaten. */
void resetMS5611(void) {
set_mrt_repeatedmode (2, (110*__SYSCLOCK_100us)); // 11ms
MS5611state= 0;
}

/* Note for readers from LPC-Forum:
I2C_MRTaction is part of the MRT-Interrupt handler which is copied here:

void MRT_IRQHandler(void) {
if (LPC_MRT->Channel[2].STAT & MRT_STAT_IRQ_FLAG) {
LPC_MRT->Channel[2].STAT = MRT_STAT_IRQ_FLAG;
I2C_MRTaction();
}
if (LPC_MRT->Channel[3].STAT & MRT_STAT_IRQ_FLAG) {
LPC_MRT->Channel[3].STAT = MRT_STAT_IRQ_FLAG;
MSBRxTiming();
}
NVIC->ICPR[0] |= MRT_INTERRUPT_BIT;
return;
}
*/

/** Bearbeitet den für den I²C-Verkehr ausgelösten MRT-Interrupt:
Startet die nächste anstehende I²C-Kommandosequenz. */
inline void I2C_MRTaction(void) {
cpyI2CSTAT= LPC_I2C->STAT;
if (MS5611state>I2CCtrlUppLimit) MS5611state= 0; // panic
(*I2CCtrlTable[MS5611state])();
MS5611state++;
return;
}

/** I²C-Interrupt: Setzt die laufende I²C-Kommandosequenz fort. */
void I2C_IRQHandler(void) {
cpyI2CSTAT= LPC_I2C->STAT;
if (MS5611state>I2CCtrlUppLimit) MS5611state= 0; // panic
if (MS5611state & MS5611statePanicFlag) I2CCtrlResetHardware();
else {
(*I2CCtrlTable[MS5611state])();
MS5611state++;
}
return;
}

/* Note for readers from LPC-Forum:
This is a data record for logging faults; it is used in the error repai function:
typedef struct sErrRecord {
uint8_t tag;
uint8_t subtag;
uint16_t SysTickStamp;
union uErrRecord {
uint32_t unspecified;
struct sI2C {
uint32_t currI2Cdata;
uint8_t cpyI2CSTAT;
} I2C;
} v;
} tErrRecord;
*/

/** Tag für Fehleraufzeichnung, identifiziert I²C-Fehler. */
#define ErrRecordI2C 1
/** Schreibt eine I²C-Fehlermeldung in das Fehler-Log.
\param stwird als SubTag eingetragen.
*/
void wErrRecordI2C(void) {
disableInterrupts;
int i= (xErrRecord++) & 0x03;
ErrRecord.tag= ErrRecordI2C;
ErrRecord.subtag= MS5611state;
ErrRecord.SysTickStamp= (uint16_t)(SysTickCounter) & 0xffff;
ErrRecord.v.I2C.cpyI2CSTAT= cpyI2CSTAT;
ErrRecord.v.I2C.currI2Cdata= input;
enableInterrupts;
setLED_BlinkPattern(LED_Blink_2Blinks);
}


Thank you very much in advance for your patience and help and
kind regards,
Helmut

Outcomes