Skip to main content
Explorer
December 29, 2024
Question

I2C start condition not generating and unknown error interrupts

  • December 29, 2024
  • 5 replies
  • 2750 views

I am trying to use stm32F446 as a master I2C device with LL library and I have 2 main issues.

1. After sending a couple of messages(always 4 at most) over I2C I get no start condition when setting CR1->START, if I pause the program a couple of seconds later and I inspect the register it is still at 1 and never goes back to 0.

2.I randomly get interrupts on both (Don't know if both at the same time) EVT and ERR with none of the EVT or ERR flags set.

Here is my code if needed:

typedef enum {
 TWI_IDLE,
 TWI_SENDING,
 TWI_RECEIVING,
 TWI_BERR,
 TWI_AF,
 TWI_ARLO,
 TWI_OVR,
 TWI_UNKNOWN_EVENT,
 TWI_UNKNOWN_ERROR,
} TWI_state;

typedef enum {
 TWI_SUCCESS,
 TWI_FAILURE,
 TWI_BUSY,
} TWI_status;

typedef struct {
 I2C_TypeDef* I2CxReg;
 TWI_state state;
 uint8_t totalBytes;
 uint8_t bytesDone;
 uint8_t* data;
 uint8_t sendAddress;
} TWI_interface;

static void resetTWI(TWI_interface* iface);

TWI_status TWIMasterTransmit(TWI_interface* iface, uint8_t address, uint8_t* data, uint8_t length) {
 TWI_status status = TWI_SUCCESS;
 if (iface->I2CxReg == NULL) {
 status = TWI_FAILURE;
 }
 else if (iface->state == TWI_IDLE) {
 iface->totalBytes = length;
 iface->bytesDone = 0;
 iface->data = data;
 iface->sendAddress = address << 1; //addr+w
 LL_I2C_GenerateStartCondition(iface->I2CxReg);
 iface->state = TWI_SENDING;
 }
 return status;
}
void TWIEventHandler(TWI_interface* iface) {

 //Master transmit
 if (LL_I2C_IsActiveFlag_SB(iface->I2CxReg)) {
 LL_I2C_TransmitData8(iface->I2CxReg, iface->sendAddress);
 }
 else if (LL_I2C_IsActiveFlag_ADDR(iface->I2CxReg) || LL_I2C_IsActiveFlag_ADD10(iface->I2CxReg)) {
 LL_I2C_ClearFlag_ADDR(iface->I2CxReg);
 if (iface->bytesDone == 0) {
 LL_I2C_TransmitData8(iface->I2CxReg, iface->data[iface->bytesDone]); //bytesDone=0
 iface->bytesDone++;
 }
 }
 else if (LL_I2C_IsActiveFlag_TXE(iface->I2CxReg) || LL_I2C_IsActiveFlag_BTF(iface->I2CxReg)) {
 if (iface->bytesDone < iface->totalBytes) {
 // Transmit data
 LL_I2C_TransmitData8(iface->I2CxReg, iface->data[iface->bytesDone]);
 iface->bytesDone++;
 }
 else if (LL_I2C_IsActiveFlag_BTF(iface->I2CxReg)) {
 LL_I2C_GenerateStopCondition(iface->I2CxReg);

 // Sometimes it waits forever, so we time out.
 uint16_t timeout = 1000;
 while (timeout > 0 && (iface->I2CxReg->CR1 & I2C_CR1_STOP)) {
 timeout--;
 }
 if (timeout == 0) {
 resetTWI(iface);
 }
 }
 }
 else {
 iface->state = TWI_UNKNOWN_EVENT;
 }
}


void TWIErrorHandler(TWI_interface* iface) {
 //Note: State should be reseted to TWI_IDLE manually
 // Maybe do fix the i2c issue here
 if (LL_I2C_IsActiveFlag_BERR(iface->I2CxReg)) {
 iface->state = TWI_BERR;
 LL_I2C_ClearFlag_BERR(iface->I2CxReg);
 }
 else if (LL_I2C_IsActiveFlag_AF(iface->I2CxReg)) {
 iface->state = TWI_AF;
 LL_I2C_ClearFlag_AF(iface->I2CxReg);
 }
 else if (LL_I2C_IsActiveFlag_ARLO(iface->I2CxReg)) {
 iface->state = TWI_ARLO;
 LL_I2C_ClearFlag_ARLO(iface->I2CxReg);
 }
 else if (LL_I2C_IsActiveFlag_OVR(iface->I2CxReg)) {
 iface->state = TWI_OVR;
 LL_I2C_ClearFlag_OVR(iface->I2CxReg);
 }
 else {
 iface->state = TWI_UNKNOWN_ERROR;
 }
}

static void resetTWI(TWI_interface* iface) {
 iface->state = TWI_IDLE;
 iface->totalBytes = 0;
 iface->bytesDone = 0;
 iface->data = NULL;
 iface->sendAddress = 0;
 LL_I2C_EnableReset(iface->I2CxReg);
 LL_I2C_DisableReset(iface->I2CxReg);
}

    This topic has been closed for replies.

    5 replies

    Super User
    December 30, 2024

    > 1. After sending a couple of messages(always 4 at most) over I2C I get no start condition when setting CR1->START, if I pause the program a couple of seconds later and I inspect the register it is still at 1 and never goes back to 0.

    I don't know the reason, but observing the activity on the bus using oscilloscope or logic analyzer (LA) may shed some more light perhaps.

     

    > 2.I randomly get interrupts on both (Don't know if both at the same time) EVT and ERR with none of the EVT or ERR flags set.

    Maybe consequence of late interrupt source clear - it should be most probably ignored.

    JW

    Explorer
    December 30, 2024

    @waclawek.jan 
    1. I did use a logic analyzer and start condition is in fact not generated (line stays at logic 1)
    I implemented the following routine to restart the peripheral:

     uint8_t tries = 0;
    
     while (tries < MAX_TRIES && iface->state == TWI_IDLE) {
     initForSending(iface, address, data, length, false);
     LL_I2C_GenerateStartCondition(iface->I2CxReg);
     uint16_t timeout = 5000;
     while (timeout > 0 && (iface->I2CxReg->CR1 & I2C_CR1_START)) {
     timeout--;
     }
     if (timeout == 0) {
     resetTWI(iface);
     }
     else {
     iface->state = TWI_SENDING;
     }
     tries++;
     }
     if (iface->state != TWI_SENDING) {
     status = TWI_FAILURE;
     iface->state = TWI_IDLE;
     }

    It still doesn't work. It seems sometimes start bit is being set but also transmission never starts for some reason

    2.Thanks, will ignore it

    Super User
    December 31, 2024

    Show a screenshot of the I2C registers when the error occurs. Probably missing something in there. Note that BUSY can cause this behavior.

     

    You're cleaning the flags in one function, then returning to the original interrupt function. I wouldn't expect the interrupt to be re-triggering here. Possible, but usually this only happens if clearing flags is the last thing you do.

    Explorer
    December 31, 2024

    @TDK 
    Here is the debug information when error occurs (error being communication not starting)
    Here is the TWI1 object I am using:

    Dioswilson_0-1735610503625.png

    As we can see, the state switched to TWI_SENDING, which should mean start condition was sent but as we can see on the logic analizer, fifth start condition is never sent.

    Dioswilson_1-1735611025492.png

    Here are the registers:

    Dioswilson_2-1735611104637.png

    Dioswilson_4-1735611159644.png

     

     

     

     

    Super User
    December 31, 2024

    Your register output shows all 0s in the I2C1 registers. The peripheral is not even enabled, it's certainly not going to send a start condition.

    I also do not see CR1_START high like you say it is in the first post.

    > As we can see, the state switched to TWI_SENDING

    Looks like you have a modified HAL-like structure. You should be initializing the peripheral in a manner similar to HAL_I2C_Init. The values in the software state handle are not what matters; ultimately the registers dictate how the peripheral behaves.

    Super User
    December 31, 2024

    >  I didn't notice PE was 0, but I can ensure that I don't set it to 0 manually

    You do that in resetTWI() --> LL_I2C_EnableReset(), indirectly, by setting SWRST.

    After that, you would need to reinitialize the whole I2C machine.

    However, the primary problem is that you call resetTWI() at all, so you should investigate there.

    You may want to output some tracking/state information onto one of the pins (e.g. using UART at high baudrate) and observe that together with the I2C bus.

    JW

     

    Explorer
    December 31, 2024

    Thanks for the information, I thought SWRST didn't update the configuration registers.
    Disabling the interface and reenableing it works fine. 
    How could I investigate the need for reset? What should I send through UART? I2C registers?
    I've noticed reseting is not necessary on the SB (which I added later) but it's needed when checking for STOP to clear (In case it isn't cleared, I reset the peripheral on a timeout)

    Super User
    January 2, 2025

    > What should I send through UART? I2C registers?

    No, or at least not primarily. My idea is, that you trace the progression of code through the I2C state machine, including the error handling; i.e. at each key point where something is written to the I2C registers, I would output some distinctive mark, which could be observed together with the actual behaviour of the I2C bus (there are quite a few of those changes, that's why I suggested UART).

    In other words, you would want to know, what happened a couple of steps before the machine stuck.

    > here are the registers AFTER the start bit wasn't generated:

    But that's after the timeout, isn't it. If there was some error/status flag, it would have been already cleared by the error interrupt, wouldn't it.

    At this point, I would also ask, what exactly is connected and how, and whether signal integrity of the bus is perfect or not that much so (that involves track length, capacitance, pullups, neighbouring tracks with potential crosstalk, and all aspects of ground arrangement).

    In my experience, the v1 I2C in 'F4 has a flaw (unacknowledged by ST, so take this with a huge grain of salt) in that due to unexpected interference on the bus it can get stuck amidst communication without any indication (my guess is, that this is due to flawed multimaster-related logic (I've seen that on non-ST chips in the past, unacknowledged by that manufacturer either), but I have no proof and I don't have insider information on the I2C internal machine to draw definitive conclusions). So, if the answer of the above question is "no, I am not 100% sure about signal integrity, as my tracks are not very short, pullups are weak, ground is not that great, and there are sources of interference nearby", you definitively need the timeout to bail out, but you should primarily focus on improving that signal integrity.

    JW

     

    Explorer
    January 2, 2025

    But that's after the timeout, isn't it. If there was some error/status flag, it would have been already cleared by the error interrupt, wouldn't it.
    While it is true that status registers' were cleared if an error occured, this is not the case, since in case of errors I change the state variable in my struct, and that didn't happen. It has the value it takes when START bit is set on CR1

    > Signal integrity is not perfect. It is a breadboard, so tracks are short. But I did check and GNDs are 0.3v apart.
    Reducing pullup resistors also semmed to help. Will take this in consideration for the final product but still check for errors just in case and reset the interface I guess

    Super User
    January 3, 2025

    > . It is a breadboard

    If solderless, I recommend you to throw it to garbage bin today and never look back.

    Soldered breadboards may be OK for prototyping, but these days when PCBs are manufactured cheaply and quickly on demand, there's little reason for not to make prototypes closely resembling the final product.

    > still check for errors just in case and reset the interface I guess

    I strongly recommend to do so.

    JW

    Super User
    January 2, 2025

    OAR1 = 0x4018 is interesting. Since it's a master, why is this register modified? The highest bit set is in a "reserved" section of the register. Not sure if it's relevant.

    Putting a logic analyzer on the SDA/SCL lines would provide some insight.

    Explorer
    January 2, 2025

    I did check SDA/SCL lines. All messages are sent correctly until this error happens (And start bit isn't sent).
    OAR1 is modified because it has a random slave address I set on CubeMX