cancel
Showing results for 
Search instead for 
Did you mean: 

I2C and interrupts on SCL

sb_st
Associate III

Hi;

I have a tlv493d magnetic encoder, with which I would like to communicate via i2c with an stm32f4 MCU. 

I can successfully initiate communication with this sensor via I2c, to perform an initial configuration of the sensor. I can verify this by way of an oscilloscope and a logic analyzer - my initial read/write transactions all seem fine.

One of the properties of the tlv493d is that - once it's been initialized - it indicates that it has data ready to be read by periodically pulling the SCL line low. To accommodate this, I have - in addition to my I2C lines - a GPIO that uses EXTI to sense this interrupt. 

The relevant parts of my code (I am working cubeIDE, using HAL) are:

 

 

// Sensor interrupt callback
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if (GPIO_Pin == SENSOR_INT_Pin) {
		tlv493d_data_ready = 1;
	}
}

// Main code
void sensor_read_task(void *argument)
{
	// Initialize the sensor. This consistently returns
	// HAL_OK, and I can verify the sensor is working 
	// with a logic probe.
	sensor_status = TLV493D_initialize( &sensor, &sensor_config);
	
	// Enable interrupts on the GPIO	
	HAL_NVIC_EnableIRQ(EXTI4_IRQn);

	while (1)
	{
		if (tlv493d_data_ready > 0 ) {
			HAL_NVIC_DisableIRQ(EXTI4_IRQn);
			sensor_status = TLV493D_read_data(&sensor);
        		tlv493d_data_ready = 0;
        		HAL_NVIC_EnableIRQ(EXTI4_IRQn);
		}
	}
}

 

 

 

My problem is that in the main while() superloop, my TLV493D_read_data() function always returns HAL_BUSY, and I cannot get meaningful data from the sensor. 

Trolling around these forums, I see mention of resetting the I2C bus (though there are also accompanying comments that having to do this usually indicates problems elsewhere). When I try to add some reset logic to my read function, like this:

 

 

HAL_StatusTypeDef TLV493D_readRegisters(TLV493D_HandleTypeDef *sensor,
                                    	uint8_t num_registers_to_read, uint8_t *data) {
	HAL_StatusTypeDef status;
	uint32_t i2c_error;

	// Verify device is ready before read
	status = HAL_I2C_IsDeviceReady(sensor->i2c_handle,
					TLV493D_I2C_ADDR,
					3,  // Number of trials
					100);  // Timeout

	if (status != HAL_OK) {
		// Explicitly reset I2C peripheral
		if (HAL_I2C_DeInit(sensor->i2c_handle) != HAL_OK) {
			return HAL_ERROR;
		}

		if (HAL_I2C_Init(sensor->i2c_handle) != HAL_OK) {
			return HAL_ERROR;
		}
	}

	// Verify device is ready before read
	status = HAL_I2C_IsDeviceReady(sensor->i2c_handle,
					TLV493D_I2C_ADDR,
					3,  // Number of trials
					100);  // Timeout
	if (status != HAL_OK) {
		// Log that device is not responding
		return status;
	}

	// Attempt read with reduced timeout for diagnostics
	return HAL_I2C_Master_Receive(sensor->i2c_handle,
					STLV493D_I2C_ADDR,
					data,
					num_registers_to_read,
					HAL_MAX_DELAY);

 

 

 I notice that read transactions DO seem to work, though they are now wholly unrelated to the actual interrupt pulse being sent. 

Here, visually, is what's happening as seen by a logic analyzer. In the below image, we can see the (lower) SCL line being pulled down periodically by the sensor. I can see in a debugger that the interrupt callback is indeed being called properly, and the read function attempts to run, but always returns HAL_BUSY, and we can see no pulses on the SDA line. 

Screenshot 2024-12-16 at 10.05.07 AM.png

Below, in contrast, is what happens when I reset the I2C bus inside my read function (which I think is probably happening every time it fires). I would expect to see very small bursts of read transactions on SDA, with long periods of relative silence between the interrupt pulses. Instead, the read transaction seems to be constantly called, as though it is triggering interrupts itself, despite me disabling them in the code above when trying to do a read. 

Screenshot 2024-12-16 at 10.10.32 AM.png

I've tried this same setup on a Nucleo board as well as on a homemade development board, with similar results. Further, I can hook this sensor to a separate esp32 development board I have and it runs flawlessly, so I think the sensor hardware itself is sound. 

How else can I troubleshoot this? I've tried everything I can think to search for online, and am stumped. 

 

2 REPLIES 2
TDK
Guru

If SCL is low when you try to do an I2C transaction, the chip will return HAL_BUSY because the bus is busy. If you set the interrupt to happen on a rising edge instead of a falling edge, it should fix this.

SCL getting pulled low by a slave seems like a recipe for a confused bus master.

> My problem is that in the main while() superloop, my TLV493D_read_data() function always returns HAL_BUSY, and I cannot get meaningful data from the sensor. 

It would be helpful to see the code of this function. Should be able to instrument and see where in the HAL library it triggers the HAL_BUSY.

If you feel a post has answered your question, please click "Accept as Solution".
sb_st
Associate III

 

If SCL is low when you try to do an I2C transaction, the chip will return HAL_BUSY because the bus is busy. If you set the interrupt to happen on a rising edge instead of a falling edge, it should fix this.

Oh interesting - you're suggesting my I2C transaction might be starting before the sensor has released the SCL line. I have tried inserting an osDelay() after returning from my interrupt callback, but maybe I need to wait longer...or, as you say, trigger on the rising rather than the falling edge. I'll try that, thanks!

 


It would be helpful to see the code of this function. Should be able to instrument and see where in the HAL library it triggers the HAL_BUSY.


Sure, I actually included it in the first code snippet above. Here it is again:

 

// Main code
void sensor_read_task(void *argument)
{
	// Initialize the sensor. This consistently returns
	// HAL_OK, and I can verify the sensor is working 
	// with a logic probe.
	sensor_status = TLV493D_initialize( &sensor, &sensor_config);
	
	// Enable interrupts on the GPIO	
	HAL_NVIC_EnableIRQ(EXTI4_IRQn);

	while (1)
	{
		if (tlv493d_data_ready > 0 ) {
			HAL_NVIC_DisableIRQ(EXTI4_IRQn);
			sensor_status = TLV493D_read_data(&sensor);
        		tlv493d_data_ready = 0;
        		HAL_NVIC_EnableIRQ(EXTI4_IRQn);
		}
	}
}

 

Something else I might be getting wrong here: this sensor doesn't seem to need/use individual register addressing when doing reads. From the sensor's datasheet:

  • The sensor addressing consists of one byte followed by one ACK bit. The purpose of the addressing is to identify (slave number) the sensor with which the communication should take place. These bits are required independently of whether only one or multiple slaves in bus configuration are connected to the master. The master sends 7 address bits starting with the MSB followed by one read/write bit (read = high, write = low). The slave (sensor) responds with one ACK bit. Every bit in the SDA line is pulled down or up while SCL is low, then SCL is pulled up for a pulse and again down before the SDA line is pulled again down.
  • Each data transfer consist of one byte data followed by one ACK bit. If the LSB bit of the sensor addressing byte was a write (low), then the master writes 8 data bits to the slave and the slaves responds with one ACK bit. If the LSB bit of the sensor addressing was a read (high), then the master reads 8 data bits from the slave and the master responds with one ACK bit. Data bytes start always with the register address 00H and as many bytes will be transferred as the SCL line is generating pulses (following a 9 bit pattern), till the stop condition

 

In my initialization code, I do a HAL_I2C_Master_Transmit(), sending a single 0x00 byte, which the datasheet says is meant to reset it. Then I do a HAL_I2C_Master_Receive(), which is successful. 

 

But, as I mentioned, subsequent calls to HAL_I2C_Master_Receive() always report HAL_BUSY. Am I meant to be preceding all HAL_I2C_Master_Receive() calls with HAL_I2C_Master_Transmit() of that 0x00 byte, to tell the sensor I want to start reading from register 0? Or is it enough to just send the HAL_I2C_Master_Receive() on its own? I'm a little confused about whether I should think of a read operation as one single transaction or two. 

Thank you again for the time!