cancel
Showing results for 
Search instead for 
Did you mean: 

stm32f4 interrupts and i2c

sb_st
Associate III

Hello;

I'm writing a driver for a tlv493d magnetic encoder using CubeIDE. This encoder has a peculiarity where it pulls the SCL line of an i2c bus low when it has data ready to be read, at which point the MCU needs to initiate an i2c receive transaction to clear the interrupt and read the data. 

Obviously we don't want the SCL clock pulses to trigger further interrupts, so I think I need to 'do something' to accommodate that. I'm struggling to understand what that 'something' is for this MCU, and how to configure it in this setup.

So far, I have configured my i2c1 bus using cubeMX, and can successfully initially communicate with the TLV (to configure registers after a reset). Then, I try to reconfigure the pin as GPIO-exti to receive the interrupt, but this seems to 'break' its connection to i2c. And I'm wondering if this - switching the pin's function back and forth between i2c and gpio-exti - is what I should ideally be doing.

My question: is it possible for a pin to be configured to both serve as the SCL line for i2c  AND receive interrupts when the line is pulled low? Or am I on the right track with this switching-back-and-forth thing I'm doing? 

(I understand that there's another layer here, which is whether I use HAL, LL, or registry writing to achieve this; right now I'm just trying to wrap my head around this apparent dual-functionality this pine is apparently meant to have). 

thanks!

1 ACCEPTED SOLUTION

Accepted Solutions
sb_st
Associate III

I was finally able to solve this - I'll leave what I did here in case any more intrepid explorers chance upon this thread.

Before I do though, I'll share something that was useful to understand, and that's the mention of 'connecting an additional GPIO' as mentioned in a comment by @Techn. The TLV493D periodically pulls the SCL line low to signal it has data ready to be read. I've been laboring under the (mistaken) assumption that this meant I needed to configure a single pin on the MCU to both function as the SCL line of an i2c connection as well as a GPIO to detect an interrupt.

An insight that is only obvious to me in hindsight is that an easier way to handle this sensor is to connect its SCL pin two my MCU via two physical traces -  one connection to the MCU's SCL line, and a separate, second connection to a separate GPIO, which can be configured to receive interrupts. This approach simplifies things considerably, and can be configured fully in cubeMX, without requiring the manual coding that I show below. For some reason I had just subconsciously assumed that pin connections are always 1:1, but there's no reason that needs to be true. 

 

In any case, I'm doing this to learn, so I wanted to share the 'use one pin for two functions' solution as well:

	// First, we need to enable SYSCFG Clock. We need to
	// do this so that we can configure EXTI.
	//
	// Normally this would all be handled by cubeIDE. However,
	// we're doing things manually here.
    __HAL_RCC_SYSCFG_CLK_ENABLE();

    // Next, we need to map Line 6, Port B (which is our SCL pin)
    // to EXTI.
    LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTB, LL_SYSCFG_EXTI_LINE6);

    // ChatGPT suggested also ensuring the pin mode on this pin
    // is set as EXTI needs. I found this not to be necessary in
    // this example, but leaving it here anyway.
    // LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_6, LL_GPIO_MODE_INPUT);

    // Next, we enable the interrupt on Line 6
    LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_6);

    // And, we say that we want the interrupt to
    // happen on falling edges (i.e. when SCL is pulled low)
	LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_6);

	// Finally, we configure NVIC to be aware that EXTI is active
	HAL_NVIC_SetPriority(EXTI9_5_IRQn, 5, 0);

	// and we enable our Interrupt Request Handler.
	HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

Note the particular priority I chose for EXTI (5) was chosen because I'm using FreeRTOS, which has some constraints on interrupt priorities that need to be satisfied. This jammed me up for a bit. 

View solution in original post

5 REPLIES 5
gbm
Lead III

Simple: Do not switch, do not use HAL for EXTI. Any port line, no matter how it's configured used by peripherals, may still be used by EXTI. So, configure the I2C with HAL, then configure it for EXTI without HAL. I have no idea if LL config will work in this case, maybe yes. Then, before starting I2C transfer, deactivate EXTI interrupt or this line and restore it after I2C xfer is done.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
sb_st
Associate III

Thank you - understanding that these two functions (interrupts and i2c) should be able to coexist simultaneously is useful. I'm still having trouble, so if I may, I'd like to outline what I'm doing in the hopes someone can point out what I'm getting wrong. 

I'm using an stm32f405rgt6, and communicating via the i2c1 bus. My SCL/SDA lines are on pins PB6 and PB7, respectively. 

Reading the reference manual for this MCU, I think I understand that - if I want to detect an interrupt being asserted on the falling edge of the SCL line - I should enable EXTI Line 6. 

So, taking the advice above, I enable i2c1 via HAL. I configure my sensor, and can verify with an oscilloscope that after initial configuration, it is pulling the SCL line low periodically. 

After configuration, I think I need to:

  • Enable interrupts on EXTI_Line_6
  • Enable a falling trigger on EXTI_Line_6

I do this using LL functions:

LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_6);
LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_6);

I think I also need to enable the EXTI9_5_IRQNn controller, which I do using HAL:

HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

Finally, I think - because cubeMX has not automatically generated this code - that I need to furnish the following:

void EXTI9_5_IRQHandler(void)
{
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
}

void HAL_GPIO_EXT1_Callback( uint16_t GPIO_Pin) {

	if (GPIO_Pin == GPIO_PIN_6) {
		set_data_ready_flag = 1;
	}
}

There are a lot of dots to connect, and I'm unsure I've done it all correctly. When I step through this with a debugger, my code never seems to enter the EXTI9_5_IRQHandler. This suggests to me I've failed to configure something properly, or am otherwise missing something?

Thank you!

sb_st
Associate III

Still trying to work my way through this, and I uncovered what might be a clue: I notice that when I upload my code to my board and just connect it to power, my tlv493d sensor periodically pulls the SCL line low as I expect.

However, when I connect the board to an stlink debugger and enter debug mode, I do not see the sensor pulling the SCL low - even without setting any breakpoints. 

I'm curious what causes this difference in behavior? 

You can connect additional gpio, disable interrupt or use flag to indicate when you don't transmit via i2c. This is much simpler and can address time criticality. Other wise you have to follow the timing diagram of your i2c slave, do the switching.

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

I was finally able to solve this - I'll leave what I did here in case any more intrepid explorers chance upon this thread.

Before I do though, I'll share something that was useful to understand, and that's the mention of 'connecting an additional GPIO' as mentioned in a comment by @Techn. The TLV493D periodically pulls the SCL line low to signal it has data ready to be read. I've been laboring under the (mistaken) assumption that this meant I needed to configure a single pin on the MCU to both function as the SCL line of an i2c connection as well as a GPIO to detect an interrupt.

An insight that is only obvious to me in hindsight is that an easier way to handle this sensor is to connect its SCL pin two my MCU via two physical traces -  one connection to the MCU's SCL line, and a separate, second connection to a separate GPIO, which can be configured to receive interrupts. This approach simplifies things considerably, and can be configured fully in cubeMX, without requiring the manual coding that I show below. For some reason I had just subconsciously assumed that pin connections are always 1:1, but there's no reason that needs to be true. 

 

In any case, I'm doing this to learn, so I wanted to share the 'use one pin for two functions' solution as well:

	// First, we need to enable SYSCFG Clock. We need to
	// do this so that we can configure EXTI.
	//
	// Normally this would all be handled by cubeIDE. However,
	// we're doing things manually here.
    __HAL_RCC_SYSCFG_CLK_ENABLE();

    // Next, we need to map Line 6, Port B (which is our SCL pin)
    // to EXTI.
    LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTB, LL_SYSCFG_EXTI_LINE6);

    // ChatGPT suggested also ensuring the pin mode on this pin
    // is set as EXTI needs. I found this not to be necessary in
    // this example, but leaving it here anyway.
    // LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_6, LL_GPIO_MODE_INPUT);

    // Next, we enable the interrupt on Line 6
    LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_6);

    // And, we say that we want the interrupt to
    // happen on falling edges (i.e. when SCL is pulled low)
	LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_6);

	// Finally, we configure NVIC to be aware that EXTI is active
	HAL_NVIC_SetPriority(EXTI9_5_IRQn, 5, 0);

	// and we enable our Interrupt Request Handler.
	HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

Note the particular priority I chose for EXTI (5) was chosen because I'm using FreeRTOS, which has some constraints on interrupt priorities that need to be satisfied. This jammed me up for a bit.