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!
2024-12-18 04:31 AM
I don't know if this is a further clue, but observing my SDA/SCL lines with a log probe throughout the initialization process, I'm noticing the "Setup to read to [0x7F+NAK]" bit off to the right there. Does that indicate something amiss with the communication just prior?
2024-12-18 05:09 AM
The last transaction looks okay to me.
I would instrument the code to understand where HAL_BUSY is being reported. The contents of the I2C handle state machine can also offer clues. Is its state READY or something else?
2024-12-18 10:03 AM
I'm using the tlv493d, but I have never tried to use the INT mode (I have INT = 0, FAST = 1, LOW_POWER = 1, reading the data every 2ms I have garanteed new data).
If You realy need the low-power mode (100Hz) then probably You really need to deinit the I2C and after the interrupt event init the I2C and read the Data.
From what I see in Your log the init seems OK (including the read).
I'm using the library from Infineon (https://softwaretools.infineon.com/tools/com.ifx.tb.tool.xensivtlx493dgenericlibrarysoftware)
The low level write and read function are:
int32_t ReadHallSensor(uint8_t addr, uint8_t *data, uint8_t count)
{
HAL_StatusTypeDef ret;
if(xSemaphoreTake(sI2CMutex, 100) == pdPASS)
{
ret = HAL_I2C_Master_Receive_IT(&hi2c1, addr, data, count);
if(ret == HAL_OK)
{
// wait for Complete event
if(xSemaphoreTake(sI2CRXEndEvent, 5 + (count / 8)) == pdTRUE)
{
xSemaphoreGive(sI2CMutex);
return(0);
}
else
{
xSemaphoreGive(sI2CMutex);
return(1);
}
}
}
else
{
return(1);
}
return(1);
}
int32_t WriteHallSensor(uint8_t addr, uint8_t* data, uint8_t count)
{
HAL_StatusTypeDef ret;
if(xSemaphoreTake(sI2CMutex, 100) == pdPASS)
{
ret = HAL_I2C_Master_Transmit_IT(&hi2c1, addr, data, count);
if(ret == HAL_OK)
{
// wait for Complete event
if(xSemaphoreTake(sI2CTXEndEvent, 5 + (count / 8)) == pdTRUE)
{
xSemaphoreGive(sI2CMutex);
return(0);
}
else
{
xSemaphoreGive(sI2CMutex);
return(1);
}
}
}
else
{
return(1);
}
return(1);
}
2024-12-19 06:50 AM
Hi @TDK, thank you!
I would instrument the code to understand where HAL_BUSY is being reported
I'm sorry, can you elaborate what you mean by "instrument the code"? I am using a logic analyzer while stepping through code with a debugger, to view all the steps of what I am seeing.
Here, for example, are screen grabs of what the I2C transaction looks like when this sensor is attached to an esp32 using nearly-identical driver code (and which works successfully):
And here is a screen grab of the I2C I see when running my code on the stm32:
They look alright to me, or at least I see nothing obvious that would suggest the transactions themselves are incorrect. So I'm thinking there's something else going on here.
Another clue: if I do like @MHoll.2's comment below mentions and disable interrupts on the sensor, and just periodically read it ever 10 ms, everything works as expected. So my trouble here seems related to the use of interrupts.
The contents of the I2C handle state machine can also offer clues. Is its state READY or something else?
Hm, thanks for pointing me to this. I tried modifying my superloop to:
while (1)
{
if (tlv493d_data_ready) {
LL_EXTI_DisableIT_0_31(LL_EXTI_LINE_4);
sensor_status = HAL_I2C_IsDeviceReady(sensor_config.i2c_handle,
SB_TLV493D_I2C_ADDR,
3, // Number of trials
100); // Timeout
while (sensor_status != HAL_OK) {
// Explicitly reset I2C peripheral
sensor_status = HAL_I2C_DeInit(sensor_config.i2c_handle);
sensor_status = HAL_I2C_Init(sensor_config.i2c_handle);
// Verify device is ready before read
sensor_status = HAL_I2C_IsDeviceReady(sensor_config.i2c_handle,
SB_TLV493D_I2C_ADDR,
3, // Number of trials
100); // Timeout
}
sensor_status = SB_TLV493D_read_data(&sensor);
tlv493d_data_ready = 0;
LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_4);
}
}
In the above, the HAL_I2C_IsDeviceReady() on Line 5 returns HAL_BUSY. When I expect the state of the handle at the this point, I see:
Stepping through the I2C DeInit/Init loop there, the I2C reset seems to work but HAL_I2C_IsDeviceReady() continues to report HAL_BUSY, and the handle state continually looks like:
2024-12-19 06:54 AM
Hi @MHoll.2 thanks for the info! I'd eventually like to work towards getting I2C interrupts working, as well as semaphore stuff, but am trying to get a simplest-case setup going here. I DO see that if I disable interrupts on the tlv493d and just periodically read it using my code above, it seems to work fine. This, at least, suggests to me that the i2c transactions are structured properly. So I think my issue is related specifically to the interrupt stuff.
2024-12-19 06:41 PM
If HAL_I2C_DeviceReady is returning HAL_BUSY, by "instrument the code" I mean set a breakpoint and figure out which line is returning HAL_BUSY and determine why. In this case, it has to be this line:
> I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK
So the I2C_FLAG_BUSY flag is set. What does the manual say about that?
Your slave is pulling SCL low periodically, so it makes sense that flag would be set. How does it say it gets cleared? A stop condition.
So a stop condition is when SDA is low and SCL goes high. You'll need to generate that in order to clear the BUSY bit and let the peripheral progress. You can do that be reconfiguring the SDA/SCL pins as open-drain output, pulling SDA low, then pulling SCL low temporarily, then releasing SDA. Resetting the peripheral would also do the same thing. Something like:
__HAL_RCC_I2C1_FORCE_RESET();
__HAL_RCC_I2C1_RELEASE_RESET();
followed by re-initializing the peripheral.
Having a slave pulling SCL low periodically while the bus is idle violates I2C bus protocol, hence it will be annoying to deal with here.