2024-12-29 12:02 PM
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);
}
2024-12-30 10:59 AM
> 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
2024-12-30 02:06 PM
@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
2024-12-30 05:24 PM
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.
2024-12-30 06:13 PM
@TDK
Here is the debug information when error occurs (error being communication not starting)
Here is the TWI1 object I am using:
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.
Here are the registers:
2024-12-30 06:55 PM - edited 2024-12-30 06:58 PM
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.
2024-12-30 07:06 PM
I know that, I didn't notice PE was 0, but I can ensure that I don't set it to 0 manually, I have no clue how is it turning to 0. I am also not using pointers directly in this program.
2024-12-30 11:56 PM
> 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
2024-12-31 07:38 AM
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)
2024-12-31 09:17 AM
The reason I was reseting on start bit is because sometimes start bit is never set.
You can see it is at 1 and never goes back to 0. That is why I add a timout and disable the peripheral. Reenabling does gain some time until it breaks again, but in the end it will get stuck sometime. (With a transmission every 50ms I got a "crash" every like 30s). I don't know if I am doing something wrong or I should just restart the peripheral every time I got an error like this