2024-12-16 08:12 AM - edited 2024-12-17 04:18 AM
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.
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.
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.
2024-12-17 06:06 PM
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.
2024-12-17 06:53 PM
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:
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!