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-23 01:37 AM
@sb_st wrote:can you elaborate what you mean by "instrument the code"?
Do something so that you can see what's going on inside the software as it runs; eg,
2024-12-24 06:04 AM
Hey @TDK, this is awesome. Truly, and sincerely, thank you for recognizing the value of teaching in a clear and non-condescending way - it is a rare quality in forums, I find, and I really, really appreciate it. That's extremely cool of you.
What you're saying makes sense, and I am trying to implement it. I have this function:
void send_i2C_stop(SB_TLV493D_InitTypeDef *sensor_config){
HAL_StatusTypeDef status;
status = HAL_I2C_IsDeviceReady(sensor_config->i2c_handle,
SB_TLV493D_I2C_ADDR,
1,
HAL_MAX_DELAY);
GPIO_InitTypeDef GPIO_InitStruct = {0};
while (status != HAL_OK){
HAL_I2C_DeInit(sensor_config->i2c_handle);
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// To send a stop condition, we need to:
// -- pull SDA low (Pin 7)
// -- pull SCL low (Pin 6)
// -- release SCL (Pin 6)
// -- release SDA (Pin 7)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
HAL_Delay(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
HAL_Delay(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
HAL_Delay(20);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
HAL_Delay(20);
HAL_I2C_Init(&hi2c1);
status = HAL_I2C_IsDeviceReady(sensor_config->i2c_handle,
SB_TLV493D_I2C_ADDR,
1,
HAL_MAX_DELAY);
}
}
which is me trying to take your advice and apply it. It doesn't quite work yet, but just understanding what the problem to solve here is (a missing stop condition, which the stm32 doesn't seem to like) is massively helpful, and I admit I'm not sure I would have gotten there nearly as quickly on my own.
I observe that - once the tlv has been initialized and it starts yanking down the SCL line periodically - 'instrumenting the code' by stepping through it with a debugger becomes difficult, because the clock on the sensor is independent from the clock of the STM32. This is to say: I can step through code, but because the SCL line keeps getting pulled low independently, it makes tracing through the code confusing and difficult.
I had it in my head that the thing I needed to learn here was how to make this work, but stepping back a bit, I think the lesson I'm actually learning is that this painful rigamarole could be a very good reason to select a different part for my design. :)
Anyway, thank you again so much for your willingness to teach.
2024-12-24 06:27 AM
You're welcome. :)
What you're doing should be working as far as I understand. However, it looks like you can also use SWRST to reset the bus when BUSY is locked, which is quite a bit easier than reinitializing pins.
I'd probably pick a different part as well, if the possibility is there.
Good luck.
2024-12-24 06:43 AM
THIS WORKS!!!!:
void send_i2C_stop(SB_TLV493D_InitTypeDef *sensor_config){
HAL_StatusTypeDef status;
status = HAL_I2C_IsDeviceReady(sensor_config->i2c_handle,
SB_TLV493D_I2C_ADDR,
1,
100);
while (status != HAL_OK){
HAL_I2C_DeInit(sensor_config->i2c_handle);
__HAL_RCC_I2C1_FORCE_RESET();
__HAL_RCC_I2C1_RELEASE_RESET();
HAL_I2C_Init(&hi2c1);
status = HAL_I2C_IsDeviceReady(sensor_config->i2c_handle,
SB_TLV493D_I2C_ADDR,
1,
100);
}
}
If I understand correctly, the macros you mentioned earlier are setting that SWRST bit to 1 (reset) and then to 0 ('not reset'), which is resetting the entire I2C1 bus. Is that right? I have to squint a little at understanding what these macros are doing, and I'm still learning how I might have found those on my own.
But in any case, this is terrific! You've shown me how to read and understand these lower-level registers in a practical way, and now I can learn more about how to use them to fix stuff like this. Exactly the kind of thing I was hoping to learn by being stubborn about solving this. :)
I will gladly choose a different part - I have a handful of various magnetic encoders here, and all of this started from me wondering "why would I choose one over the other?"
Thank you again so much, and - if I may - happy holidays.
2024-12-24 08:46 AM
> If I understand correctly, the macros you mentioned earlier are setting that SWRST bit to 1 (reset) and then to 0 ('not reset'), which is resetting the entire I2C1 bus. Is that right?
It'll reset the internal I2C1 peripheral state machine without actually affecting the SDA/SCL lines at all, which will clear the BUSY bit. It should do the same as the deinit/reset/init code you posted:
SET_BIT(I2C1->CR1, I2C_CR1_SWRST);
CLEAR_BIT(I2C1->CR1, I2C_CR1_SWRST);
I think the stop condition above didn't work because when the pins are reinitialized as output, they're disconnected from the I2C peripheral. That was my mistake.
Happy holidays.