I don't think that ST understands C-level software, which is pretty bad considering that they're writing documentation and libraries in C these days. Consider the fractional divisor for the USART, documented as: baud = Fck/(16*BRRGEN)
Excellent, I say to myself. Shift to create the fixed-point fraction, divide, done. BRRGEN = FCK/baud; That's ... almost elegant the way the 16 multipliers cancel out!
But the documentation goes on to explain how it SHOULD be done. In great detail:
Example 2:
To program USARTDIV = 0d25.62
This leads to:
DIV_Fraction = 16*0d0.62 = 0d9.92
The nearest real number is 0d10 = 0xA
DIV_Mantissa = mantissa (0d25.620) = 0d25 = 0x19
Then, USART_BRR = 0x19A hence USARTDIV = 0d25.625
Example 3:
To program USARTDIV = 0d50.99
This leads to:
DIV_Fraction = 16*0d0.99 = 0d15.84
The nearest real number is 0d16 = 0x10 => overflow of DIV_frac[3:0] => carry must be added up to the mantissa
DIV_Mantissa = mantissa (0d50.990 + carry) = 0d51 = 0x33 Then, USART_BRR = 0x330 hence USARTDIV = 0d51.000
WTF? Surely the people who wrote the standard peripheral library noticed that it was simpler than that? Nope (this is the STP library):
/* Determine the integer part */
if ((USARTx->CR1 & CR1_OVER8_Set) != 0)
{
/* Integer part computing in case Oversampling mode is 8 Samples */
integerdivider = ((25 * apbclock) / (2 * (USART_InitStruct->USART_BaudRate)));
}
else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
{
/* Integer part computing in case Oversampling mode is 16 Samples */
integerdivider = ((25 * apbclock) / (4 * (USART_InitStruct->USART_BaudRate)));
}
tmpreg = (integerdivider / 100) << 4;
/* Determine the fractional part */
fractionaldivider = integerdivider - (100 * (tmpreg >> 4));
/* Implement the fractional part in the register */
if ((USARTx->CR1 & CR1_OVER8_Set) != 0)
{
tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t)0x07);
}
else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
{
tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
}
/* Write to USART BRR */
USARTx->BRR = (uint16_t)tmpreg;
Wow. That was so convoluted that I lost confidence in my own simple code. So I wrote a test program to confirm that it produced the same results as the STP code. Yep. Wow.
It got a little prettier in the Cube HAL driver. Now it's all put in a macro, instead:
/* stm32f1xx_hal_usart.c */
husart->Instance->BRR = USART_BRR(HAL_RCC_GetPCLK1Freq(), husart->Init.BaudRate);
It looks nice and clean. But the macro is still calculating fraction and integer parts separately:
/* stm32f1xx_hal_usart.h */
#define USART_DIV(__PCLK__, __BAUD__) (((__PCLK__)*25)/(4*(__BAUD__)))
#define USART_DIVMANT(__PCLK__, __BAUD__) (USART_DIV((__PCLK__), (__BAUD__))/100)
#define USART_DIVFRAQ(__PCLK__, __BAUD__) (((USART_DIV((__PCLK__), (__BAUD__)) - (USART_DIVMANT((__PCLK__), (__BAUD__)) * 100)) * 16 + 50) / 100)
#define USART_BRR(__PCLK__, __BAUD__) ((USART_DIVMANT((__PCLK__), (__BAUD__)) << 4)|(USART_DIVFRAQ((__PCLK__), (__BAUD__)) & 0x0F))
Those are NOT likely to be calculations that resolve to constants at compile time, given the layers of abstraction and indirection that appear to be standard for using these libraries.
So... just "wow." There goes my confidence in libraries from ST. It's not like I hunted through massive amounts of code looking for a bad example - the USART is about the third peripheral to deal with in the blink, hello-world progression. (and then there's the reference back to that first peripheral - the rcc. They derive, programaticaly and at significant expense, the clock frequency to divide. I dunno. That's not the way I would do things if I was writing the code. Not close. Bigger, slower, more obscure, harder to understand, and ... without obvious benefits. Sigh.)