2026-04-30 9:37 AM - edited 2026-04-30 10:17 AM
I am having an issue while trying to setup an I2C driver on my STM32F767ZI Nucleo board interfacing with an SHT31 and SGP40 I2C sensors. The code I am running first initializes the SHT31 with a write and read and then initializes the SGP40 with a write and read.
The issue I am running into is whenever I send the next message (read on whichever sensor I decide to try to configure first), the I2C peripheral sends a start bit and address as expected but adds a null byte immediately after the address. See attached example where I would expect the address of 0xB2, and the two byte message of 0x36 and 0x82 to be sent instead I see the address of 0xB2, then data of 0x00 and 0x36. It runs through the first write cycle without an issue.
Edited to elaborate on the logic analyzer captures. I2C_LA._Expected captures the moment after stepping through setting the start bit in the CR2 register. It shows the address being transmitted and then SDA returns high and SCL is set low waiting to transmit the next byte.
I2C_LA shows the same line being executed but the second send message around. This shows the address being sent along with a null byte. It also seems like the byte counter is decremented so the peripheral considers that byte as a proper sent byte even though I never write anything to the TX register...
error_t i2c_master_transmit(I2C_TypeDef *i2c_instance, uint8_t device_address, uint8_t *tx_buffer, uint16_t data_length)
{
error_t status = ERR_OK;
if(i2c_wait_for_busy_clear(i2c_instance) == ERR_OK)
{
i2c_instance->ICR |= (I2C_ICR_STOPCF | I2C_ICR_NACKCF); //clear flags if set
//configure registers for TX
i2c_instance->CR1 &= ~(0x1UL << I2C_CR2_ADD10_Pos); //set to 7 bit addressing
i2c_instance->CR2 &= ~(0xFFUL << I2C_CR2_SADD_Pos); //clear address bits
i2c_instance->CR2 |= (device_address << I2C_CR2_SADD_Pos); //set address
i2c_instance->CR2 &= ~(0x1UL << I2C_CR2_RD_WRN_Pos); //clear bit to set to write
//set number of bytes to transmit
i2c_instance->CR2 &= ~(0xFF <<I2C_CR2_NBYTES_Pos);
i2c_instance->CR2 |= ((uint8_t)data_length <<I2C_CR2_NBYTES_Pos);
i2c_instance->CR2 |= I2C_CR2_START;
//set the SB synch flag and enable interrupt
i2c_synch_flags.I2C1_WAIT_SB = true;
//i2c_enable_event_interrupts(i2c_instance);
i2c_enable_rx_tx_interrupt(i2c_instance);
while(!(i2c_instance->ISR & I2C_ISR_TC))
{
//wait for TXE
if(i2c_synch_txe_interrupt(i2c_instance) != ERR_OK)
{
status = ERR_TIMEOUT;
break;
}
//send byte
i2c_instance->TXDR = *tx_buffer;
tx_buffer++;
}
//send stop bit once data transfer is complete
i2c_instance->CR2 |= I2C_CR2_STOP;
//wait for stop bit
while(!(i2c_instance->ISR & I2C_ISR_STOPF))
{
if((i2c_instance->ISR & (I2C_ISR_NACKF)) != 0)
{
//clear NACKF
i2c_instance->ICR |= I2C_ICR_NACKCF;
break;
}
}
}
else
{
status = ERR_FAIL;
}
return status;
}Edited to place LA Traces inline for easier viewing:
I2C_LA_Expected.jpg
I2C_LA.jpg
Solved! Go to Solution.
2026-05-07 12:56 AM
> The ISR register is always clear just before setting the start bit.
Well, then that's wrong. TXE flag should be set, as the transmitter should be empty at that point.
I don't quite understand the rest of your post, but try to walk through a transmission cycle in the debugger, with stopped processor, while manually manipulating and observing the registers. An example here (at the beginning is a normal transaction; rest of that post deals with a pathologic case which you don't need to consider).
JW
2026-05-01 8:57 AM
What is I2C_ISR (and/or other relevant I2C registers') content before the "correct" and before the "incorrect" transmission?
JW
2026-05-06 1:40 PM
Thank you for your reply and my apologies for not replying sooner.
The ISR register is always clear just before setting the start bit. After the transfer is complete, the STOPF bit gets set in the ISR as expected. The CR1 and CR2 registers also look normal.
I tried using the AUTOEND function as well and looping while the number of bytes to be written (or read for that matter) have been transferred and I found it interesting (thanks to your recommendation) is that after a "correct" write TXE in the ISR register is still set even though the number of bytes written to the CR2 registers NBYTE bits have been sent. I even tried flushing the register by setting the ISR TXE bit in software (per the datasheet). I looked a bit further into my code and found that the TXE flag was blocking me from getting to the RXNE handler. I fixed that issue and I think I'm closer to my goal.
2026-05-07 12:56 AM
> The ISR register is always clear just before setting the start bit.
Well, then that's wrong. TXE flag should be set, as the transmitter should be empty at that point.
I don't quite understand the rest of your post, but try to walk through a transmission cycle in the debugger, with stopped processor, while manually manipulating and observing the registers. An example here (at the beginning is a normal transaction; rest of that post deals with a pathologic case which you don't need to consider).
JW
2026-05-07 5:50 AM
My apologies. I think the TXE flag wasn't being set because of a change I made after making this post by preloading a byte into the transmit register to see if that would make the peripheral send whatever was in the TX register instead of a null bit so the register was actually full prior to setting the start bit. I have since restored that bit of code to what is written above and the TXE bit is indeed set prior to sending the start bit and address.
I appreciate the example. I will take a look and see if I can't refine my code to work more as expected. That last bit about the pathologic case gave me a good morning chuckle. Thank you again for your help!