diff --git a/include/libopencm3/stm32/common/usart_common_f24.h b/include/libopencm3/stm32/common/usart_common_f24.h index 9edb640bf8..1d226fa7ec 100644 --- a/include/libopencm3/stm32/common/usart_common_f24.h +++ b/include/libopencm3/stm32/common/usart_common_f24.h @@ -94,10 +94,11 @@ specific memorymap.h header before including this header file.*/ /* ONEBIT: One sample bit method enable */ #define USART_CR3_ONEBIT (1 << 11) +#define USART_BRR_UPPER_MASK (0x0000fff0U) +#define USART_BRR_LOWER_MASK (0x0000000fU) #endif /** @cond */ #else #warning "usart_common_f24.h should not be included directly, only via usart.h" #endif /** @endcond */ - diff --git a/include/libopencm3/stm32/common/usart_common_v2.h b/include/libopencm3/stm32/common/usart_common_v2.h index 89b8319ee1..c68a3c63dd 100644 --- a/include/libopencm3/stm32/common/usart_common_v2.h +++ b/include/libopencm3/stm32/common/usart_common_v2.h @@ -440,6 +440,9 @@ /**@}*/ +#define USART_BRR_UPPER_MASK (0x0000fff0U) +#define USART_BRR_LOWER_MASK (0x0000000fU) + /** @defgroup usart_gtpr_values USART_GTPR Values * @ingroup usart_defines * @{ diff --git a/lib/stm32/common/usart_common_all.c b/lib/stm32/common/usart_common_all.c index 7e0ef48c51..9c254df194 100644 --- a/lib/stm32/common/usart_common_all.c +++ b/lib/stm32/common/usart_common_all.c @@ -71,7 +71,27 @@ void usart_set_baudrate(uint32_t usart, uint32_t baud) } #endif +#ifdef USART_CR1_OVER8 + if (USART_CR1(usart) & USART_CR1_OVER8) { + /* + * When using 8x oversampling instead of 16x, the calculation works slightly differently. + * We do the same main calculation as for 16x, but with the clock rate effectively doubled. + * This keeps accuracy up and gives us the best possible divider. However, to set BRR, + * we have to do some shenanigans - specifically, USARTDIV[15:4] = BRR[15:4], but + * UARTDIV[3:0] = BRR[2:0] << 1 and we are required to keep BRR[3] as 0. + */ + const uint32_t divider = ((2 * clock) + (baud / 2)) / baud; + USART_BRR(usart) = (divider & USART_BRR_UPPER_MASK) | ((divider & USART_BRR_LOWER_MASK) >> 1U); + } else { + /* + * Modified version of the formula from the datasheets that tries to improve + * the accuracy of the calculated dividers by introducing a rounding factor + */ + USART_BRR(usart) = (clock + baud / 2) / baud; + } +#else USART_BRR(usart) = (clock + baud / 2) / baud; +#endif } /*---------------------------------------------------------------------------*/