2025-11-04 11:34 AM
Hello! This is the first time I'm using an STM32L0 (...11F4Ux) and also the first time I've tried implementing an I2C IRQ Handler. The code snippet mostly works, but there's a bug in which a READ commands sends data from the previously requested register, giving bad data. Basically, the data I receive is old by 1 request, which tells me this IRQ Handler is not handling the repeated start correctly. The exception to this is that the very first READ command is correct when I start up I2C tools on a RP5. I've been trying to figure out this bug for a couple of days now, so I'd really appreciate any help from more experience developers, thank you!
Details:
I'm 99% certain the bug exist in the handler itself but I can't seem to narrow it down. Somehow data is being stored between READ commands.
void I2C1_Slave_Init(void)
{
/* Enable clocks */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
/* Configure GPIO: PA9=SCL, PA10=SDA */
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = LL_GPIO_PIN_9 | LL_GPIO_PIN_10;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; // Must have pull-ups for I2C
GPIO_InitStruct.Alternate = LL_GPIO_AF_1;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Configure I2C1 */
LL_I2C_InitTypeDef I2C_InitStruct = {0};
I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
I2C_InitStruct.Timing = 0x20404768; // 100 kHz @ 16 MHz HSI
I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
I2C_InitStruct.DigitalFilter = 0;
I2C_InitStruct.OwnAddress1 = I2C_SLAVE_ADDR;
I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
LL_I2C_Init(I2C1, &I2C_InitStruct);
LL_I2C_EnableAutoEndMode(I2C1);
LL_I2C_EnableClockStretching(I2C1);
/* Enable interrupts */
LL_I2C_EnableIT_ADDR(I2C1);
LL_I2C_EnableIT_RX(I2C1);
LL_I2C_EnableIT_TX(I2C1);
LL_I2C_EnableIT_STOP(I2C1);
/* NVIC */
NVIC_SetPriority(I2C1_IRQn, 0);
NVIC_EnableIRQ(I2C1_IRQn);
/* Enable I2C1 peripheral */
LL_I2C_Enable(I2C1);
}
/* IRQ Handler */
void I2C1_Slave_IRQHandler(void)
{
/* --- Address Match --- */
if (LL_I2C_IsActiveFlag_ADDR(I2C1))
{
uint32_t direction = LL_I2C_GetTransferDirection(I2C1);
LL_I2C_ClearFlag_ADDR(I2C1);
i2c_rx_index = 0;
i2c_tx_index = 0;
if (direction == LL_I2C_DIRECTION_READ)
{
/* Repeated START for read phase.
* Keep using last i2c_reg_pointer (don’t reset it). */
i2c_tx_len = 1;
}
}
/* --- Data Received from Master (Write Phase) --- */
if (LL_I2C_IsActiveFlag_RXNE(I2C1))
{
uint8_t byte = LL_I2C_ReceiveData8(I2C1);
if (i2c_rx_index == 0)
{
/* First byte = register address */
i2c_reg_pointer = byte;
}
else if (i2c_rx_index == 1)
{
/* Second byte = data to write */
uint8_t val = byte;
switch (i2c_reg_pointer)
{
case REG_5V_CONTROL:
g_process_5v_off = val ? 1 : 0;
i2c_buf[REG_5V_CONTROL] = val;
break;
case REG_24V_CONTROL:
g_process_24v = val ? 1 : 0;
i2c_buf[REG_24V_CONTROL] = val;
break;
/* Read-only registers */
case REG_ADC_BATT:
case REG_CHARGE_STAT:
case REG_CHECK_BTTN:
default:
break;
}
}
i2c_rx_index++;
}
/* --- Master Requesting Data (Read Phase) --- */
if (LL_I2C_IsActiveFlag_TXIS(I2C1))
{
uint8_t tx_val = 0x00;
switch (i2c_reg_pointer)
{
case REG_ADC_BATT:
tx_val = i2c_buf[REG_ADC_BATT];
break;
case REG_CHARGE_STAT:
tx_val = i2c_buf[REG_CHARGE_STAT];
break;
case REG_CHECK_BTTN:
tx_val = i2c_buf[REG_CHECK_BTTN];
i2c_buf[REG_CHECK_BTTN] = 0;
btn_pressed_awake = 0;
break;
/* Default to return the buffer content for other regs */
default:
tx_val = i2c_buf[i2c_reg_pointer];
break;
}
LL_I2C_TransmitData8(I2C1, tx_val);
}
/* --- STOP Condition (End of Transaction) --- */
if (LL_I2C_IsActiveFlag_STOP(I2C1))
{
LL_I2C_ClearFlag_STOP(I2C1);
i2c_rx_index = 0;
i2c_tx_index = 0;
i2c_tx_len = 0;
}
}
Solved! Go to Solution.
2025-11-04 11:47 AM
Only process the TXIS flag and write data after you have received ADDR. Until then, ignore TXIS flag. You'll need a software state machine to understand where in the transaction you're at.
2025-11-04 11:47 AM
Only process the TXIS flag and write data after you have received ADDR. Until then, ignore TXIS flag. You'll need a software state machine to understand where in the transaction you're at.
2025-11-04 1:13 PM
Okay thanks, I understand the idea, but I'm not sure how to ignore the TXIS flag. Based on my code I expect LL_I2C_IsActiveFlag_TXIS to execute only on the repeated start, and never before ADDR is received. In debug mode, if I set a breakpoint at LL_I2C_TransmitData8(I2C1, tx_val) in my TXIS block, I still get data without this line executing which is very confusing to me. How is that possible?
2025-11-04 1:47 PM
> Based on my code I expect LL_I2C_IsActiveFlag_TXIS to execute only on the repeated start, and never before ADDR is received.
The reference manual explains how the flag functions and when it gets set. I understand you don't want TXIS to be set, but that's now how the chip works. You will need to adjust your code to match. The reference manual explains this.
In blocking mode, data will not be sent without TXIS being asserted and data written to TXDR.