Skip to main content
February 5, 2020
Question

[Bug found] Interference occurs in the processing of similar hardware, when operated with interruption, due to the sharing of HAL subroutines.

  • February 5, 2020
  • 8 replies
  • 1952 views

When similar hardware, such as USB, or SPI, or I2C etc. is operating on the main routine, and other hardware is operated with an interrupt, the main routine's hardware suffers errors. Although the hardware has independent registers, sharing subroutines with similar hardware blocks the simultaneous use of similar hardware.

With hundreds of mega hertz processing and a good amount of memory, an STM32 needs at least one core library that operates with each hardware independently, without sharing subroutines.

This topic has been closed for replies.

8 replies

berendi
Principal
February 5, 2020

Do you have any specific code examples?

RMcCa
Senior II
February 5, 2020

First thing is to do figure out what's​ wrong with your code. Vaguely blaming STM on a public forum isn't necessarily going to help very much. Microcontroller makers assume you can read & understand data sheets and that you have enough programming and technical knowledge to get it do what you want. STM32s are made to be extremely versatile and are thus fairly complicated.

February 5, 2020

:sad_but_relieved_face:

JoniS
Senior
February 5, 2020

Can we get minimal code to reproduce the issue?

And can you be little bit more specific about the "​main routine's hardware suffers errors" part.

February 5, 2020

I saw that there are queues for RTOS and, following the same principle, I was testing the use of waiting variables, to wait for the use of the shared subroutine. And it works.

Before performing the operation with the hardware, it is checked if it is busy, then it waits to be free. As if the hardware itself was being used.

I believe that this occurs with the use of external communication, I experienced these problems with USB Host, I2C Master etc. So it may be necessary to use external devices to reproduce this problem.

For example, if a DS3231 (I2C addr 0xD0) is operated on port I2C1 and an MPU6050 (I2C addr 0xD0) on port I2C2.

The I2C2 port is accessed in the main subroutine (runs independent of interruption). And in an interruption performed by a timer, access is made to the I2C1 port.

When timer interrupt occurs, data from port I2C1 is always obtained correctly. But communication with the I2C2 port fails, the data obtained is not correct.

Similar to what happens with I2C, when trying to use timer interrupt to control access on the USB Host port. If, for example, the HS port is being accessed in the main () routine and the FS port is being accessed via interruption, then an error occurs in the data of one of the ports.

But if the subroutines for each hardware are cloned, and each hardware operates with independent core subroutines, this problem will stop.

For example, I2C1 and I2C2 do not use the same basic HAL subroutines.

The HAL driver subroutines are the core of any project made with the STM32CubeIDE, and if they are shared with hardware this always happens.

I understand that sharing routines can be interesting to avoid the consumption of memory resources, but this ends up being a secondary concern when working with higher capacity ARM.

I see that the features of the STM32F4 and STM32F7 line are very interesting, but if it is not possible to use each hardware independently, then having a lot of FLASH or RAM is unnecessary.

berendi
Principal
February 5, 2020

So there is a reentrancy issue in your code. Perhaps if you could post some actual code, it might be possible to pinpoint the exact problem, and help you fixing it.

February 5, 2020

Reentry? Yes, in a way it is a re-entry, but not on the same hardware (I2C1 != I2C2), but rather a re-entry into the HAL routine, because they are shared, this is the starting point of the post.

S.Ma
Principal
February 5, 2020

Well, I can't comment on RTOS nor dual core. I try to have functions which their data universe is mostly passed from struct pointer (the context) and not use any global variables. Normally, the function should only use stack for local variables (if the stack is deep enough, Cube default setting is too low, maybe this could be one possible reason for the strange behaviour).

I do use same functions for various peripherals this way, and actually, don't need (with IAR) to use volatile for global structure: Some globals will be written by ISR and cleared by main loop, or writeable only on one side. Such would be SW FIFO.

What's also important is interrupt priority and interrupt maximum allowed lag and duration: This is one tricky part for embedded or the SW will fail after some time.

When compiler generate code, it might already factorize code without tell you within a C file scope...

berendi
Principal
February 5, 2020

>don't need (with IAR) to use volatile for global structure

No compiler needs volatile if the optimization settings (or capabilities) are sufficiently low, all variables are effectively treated as volatile, resulting in safe but inefficient code.

>When compiler generate code, it might already factorize code without tell you within a C file scope...

Or even across C files. There is a reason why STM32CubeIDE has removed the -lto option from the compiler settings, it would expose loads of bugs in legacy code, including their precious HAL library.

Tesla DeLorean
Guru
February 5, 2020

Most subroutine should be coded in a thread-safe manner, so typically avoiding internal static variables, and use of global variables.

A lot of developers seem to like to use global variables, when structures, and objects would be far more appropriate.

Hardware peripherals frequently won't support concurrent operation, you should mutex things with complex operation and FIFOs to protect against multiple usage, say SDIO/SDMMC with FatFS, at some level you need to enforce ownership of resources to prevent multiple threads stepping on each other.

Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
berendi
Principal
February 5, 2020

The question is about using distinct peripherals concurrently, e.g. I2C1 in an interrupt handler, and I2C2 in the main thread.

Are there known cases of such "crosstalk" between communication peripherals? (except those caused by software bugs)

Tesla DeLorean
Guru
February 5, 2020

The HAL used to have a lot of issues, and potential race conditions, but by and large ST has tried to partition stuff into structures passed as handles/pointers. If people use the wrong ones, or have crosslinked global structures, or multiple structures describing the same hardware, in different states, I can see that being a problem. The HAL also poorly communicates the hazards of interrupt/callback context and what should and should not be occurring there. Especially things that block.

I think some of the ownership/mutex stuff needs to be done at an os/driver level, and the HAL shouldn't be the thing burdened with protecting everyone from themselves.

Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
February 5, 2020

As mentioned above, when using the queue, it is possible to avoid using the same HAL sub-routine before it is finished. This is the code I'm testing:

HAL_StatusTypeDef MPU6050_send(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
	acc_busy = TRUE;
 
	while(rtc_busy == TRUE)
	{
		IWDG_delay_ms(0);
	}
 
	HAL_StatusTypeDef result = HAL_I2C_Master_Transmit(hi2c, DevAddress, pData, Size, Timeout);
 
	acc_busy = FALSE;
 
	return result;
}
 
HAL_StatusTypeDef MPU6050_receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
	acc_busy = TRUE;
 
	while(rtc_busy == TRUE)
	{
		IWDG_delay_ms(0);
	}
 
	HAL_StatusTypeDef result = HAL_I2C_Master_Receive(hi2c, DevAddress, pData, Size, Timeout);
 
	acc_busy = FALSE;
 
	return result;
}

HAL_StatusTypeDef DS3231_send(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
	rtc_busy = TRUE;
 
	while(acc_busy == TRUE)
	{
		IWDG_delay_ms(0);
	}
 
	HAL_StatusTypeDef result = HAL_I2C_Master_Transmit(hi2c, DevAddress, pData, Size, Timeout);
 
	rtc_busy = FALSE;
 
	return result;
}
 
HAL_StatusTypeDef DS3231_receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
	rtc_busy = TRUE;
 
	while(acc_busy == TRUE)
	{
		IWDG_delay_ms(0);
	}
 
	HAL_StatusTypeDef result = HAL_I2C_Master_Receive(hi2c, DevAddress, pData, Size, Timeout);
 
	rtc_busy = FALSE;
 
	return result;
}

berendi
Principal
February 5, 2020

You wrote earlier

>The I2C2 port is accessed in the main subroutine (runs independent of interruption). And in an interruption performed by a timer, access is made to the I2C1 port.

HAL_I2C_Master_Transmit() and HAL_I2C_Master_Receive() are blocking functions, not to be used in interrupts.

If one of these function are called in an interrupt handler, the acc_busy/rtc_busy flag in the while loop will never be cleared, because the interrupt handler blocks the main thread from running. A classic case of deadlock.

S.Ma
Principal
February 5, 2020

ISR in general shall be as brief as possible. Sending a full I2C transaction within an ISR may make the code weaker?

Also avoid putting delay functions in the ISR.

As for the previous question, on the HW level, I2C1 and I2C2 HW peripherals are decorellated.

I2C is a relatively slow speed serial interface. If I really get multiple "sources" requesting to use it to communicate, then it's an I2C transaction FIFO with callbacks that may need to be implemented, hoping not needing "to be done within *** msec" parameters... we are getting to a peripheral OS then.

(one transaction to queue being Start Write operation Start Read Operation Stop)