Serious issue with CubeMX code generation and HAL Library. Please review.
I believe I have found a serious code creation bug in STCubeMX concerning PLL initialization on (at least) STM32F767ZI MCUs. Additionally, I take issue with some pre-processor macros used for register manipulation that allowed the code creation bug to become a very serious issue. Please read the detailed description.
We created code using the CubeMX tool and have soon discovered the following symptoms:
With compiler optimizations enabled (-O3), the MCU's timers ran with double frequency after a power-cycle. If you reset (while keeping the power enabled) the timer frequency would be correct.
Analysis showed, during init of the RCC subsystem some register values are set incorrectly. Namely the RCC_DCKFGR1 register, which controls some clock muxes and prescalers for peripherals. Please have a look at this piece of Cube generated init code taken from the SystemClock_Config() function in "main.c"
Exhibit A:
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_SAI1|RCC_PERIPHCLK_SAI2|RCC_PERIPHCLK_CLK48;
PeriphClkInitStruct.PLLSAI.PLLSAIN = 96;
PeriphClkInitStruct.PLLSAI.PLLSAIR = 2;
PeriphClkInitStruct.PLLSAI.PLLSAIQ = 3;
PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV4;
PeriphClkInitStruct.PLLSAIDivQ = 1;
PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_2;
PeriphClkInitStruct.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLLSAI;
PeriphClkInitStruct.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48SOURCE_PLL;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}As you can see, "PeriphClockSelection" includes the flag for "RCC_PERIPHCLK_SAI2" although SAI2 is configured as disabled for both blocks A and B in CubeMX. However, the associated configuration variable is NOT SET before the init struct is used for register init. Since the init struct is stack-allocated, the value of not explicitly set variables of this struct are UNDEFINED. What happens is that the register gets set to "some value" that happens to live in the RAM at the time of init. Register configuration that happens before the SAI2 config may be lost (depending on the state of RAM).
Why is SAI2 clock selection enabled but not configured in code, when it is disabled in Cube?
Now you may ask, how can setting a few bits in the register change other bits? Well that's because of the very bad design of the macro used for register editing.
Exhibit B:
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
#define READ_BIT(REG, BIT) ((REG) & (BIT))
#define CLEAR_REG(REG) ((REG) = (0x0))
#define WRITE_REG(REG, VAL) ((REG) = (VAL))
#define READ_REG(REG) ((REG))
#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
#define POSITION_VAL(VAL) (__CLZ(__RBIT(VAL)))Notice how "WRITE_REG" just sets the entire register to "VAL". In "MODIFY_REG" the macro "WRITE_REG" is used as well to set the complete register to the value "SETMASK". This macro is an absolute catastrophe and is used in many places throughout HAL. Why? Because, regardless of the value for "CLEARMASK", the register is always set to "REG | SETMASK".
I am sorry, but how has this macro passed any kind of code review?
It cost us several days to track this bug down. I hope this will be fixed in future versions of CubeMX and the HAL Library.