文章目录
  1. 1. 前言
  2. 2. 波特率计算公式
  3. 3. 16倍采样下BRR的计算
  4. 4. 8倍超采样下BRR的计算
  5. 5. 库函数代码存在的问题
  6. 6. 后记

前言

STM32的串口波特率计算本来挺简单的,只不过ST的StdPeriph以及后继者STM32Cube的USART模块计算波特率那块弄得很复杂。特写此文避免新手在这一块被函数库误导了。

波特率计算公式

STM32F1xx系列波特率计算只有一个16倍采样的公式:
$$ Tx/Rx\ Buad = \dfrac{PCLK}{16*USARTDIV} $$
F2xx之后的系列增加了8倍超采样的模式,将公式里的16换成8就行。
关于N倍超采样(Oversampling),指接收端以高于波特率的时钟来采集波形,多次采集的结果用少数服从多数的原则判定电平的高低,以减少因线路干扰带来的误码。

16倍采样下BRR的计算

STM32的usart波特率生成支持小数分频,BRR寄存器高12位是整数部分,低4位是小数部分。于是:
$$ BRR=\dfrac{PCLK}{16*Baud}<<4=PCLK/Baud $$
So easy!你看硬件设计师都设计好了,波特率计算就这么简单。
有些新手可能有点迷糊,其实用定点小数的计算都是这样的思路,BRR就是个带4bit小数的定点小数而已。
分析一下误差,如果直接用BRR=PCLK/Baud,余数被截掉了,产生的截尾误差最大1个bit,对于4位小数也就是1/16=0.0625,
想要计算更精确一点就考虑舍入,BRR=(PCLK+Baud/2)/Baud,此时分频系数的舍入误差最大0.5bit,也就是0.03125,减少了一半,推荐用此方法。

实际由于STM32已经做了小数分频,误差已经很小了。只有当外设时钟设置的比较低(例如要考虑节能原因),而所需波特率又比较高时,才有必要考虑误差的问题。
可以用这个STM32波特率计算Excle表格试算BRR和误差量。这有一个STM32常见波特率的计算结果

8倍超采样下BRR的计算

至于8倍超采样的,跟16倍的一回事,只不过此时BRR寄存器小数位只用了3位,整数位与小数位之间中间空了1个bit,要做一下移位。
先假设整数位与小数位是连续的来计算:
BRR=(PCLK/(8*Baud))<<3=PCLK/Baud,结果公式一模一样,然后将整数部分左移1位就行了。完整代码如下:

1
2
3
4
uint16_t Div = (PCLK + BAUD/2)/BAUD;
uint16_t DIV_Mantissa = (Div & ~0x7)<<1;
uint16_t DIV_Fraction = Div & 0x07;
BRR = DIV_Mantissa | DIV_Fraction;

8倍超采样对波特率精度没什么影响,只影响误码率,好处是可以提高波特率的上限。例如8M时钟,用16倍超采样,波特率最高只能做到500k。改成8倍超采样,波特率最高就可以到1M了。

库函数代码存在的问题

不管16倍还是8倍,换算公式都这么简单,不知道函数库里为什么弄那么复杂。
stm32f10x_stdperiph_lib_3.1.x是这样写的

1
2
3
4
5
6
7
8
9
10
11
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
{//....前面一段省略...
/* Determine the integer part */
integerdivider = ((0x19 * apbclock) / (0x04 * (USART_InitStruct->USART_BaudRate)));
tmpreg = (integerdivider / 0x64) << 0x04;
/* Determine the fractional part */
fractionaldivider = integerdivider - (0x64 * (tmpreg >> 0x04));
tmpreg |= ((((fractionaldivider * 0x10) + 0x32) / 0x64)) & ((uint8_t)0x0F);
/* Write to USART BRR */
USARTx->BRR = (uint16_t)tmpreg;
}

不知道你绕晕了没有,我是晕了。只能说这个程序员对定点小数的计算不熟。

stm32f10x_stdperiph_lib_3.4.0增加了8倍超采样的情况,一脉相承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
{//....前面一段省略...,以下修改了一下缩进,要不太长了
/* 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;
}

到了STM32Cube时代,换算公式改成了宏。以下是来自STM32Cube_FW_F4_V1.5.0。

1
2
3
4
5
6
7
8
9
#define UART_DIV_SAMPLING16(_PCLK_, _BAUD_) (((_PCLK_)*25)/(4*(_BAUD_)))
#define UART_DIVMANT_SAMPLING16(_PCLK_, _BAUD_) (UART_DIV_SAMPLING16((_PCLK_), (_BAUD_))/100)
#define UART_DIVFRAQ_SAMPLING16(_PCLK_, _BAUD_) (((UART_DIV_SAMPLING16((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)
#define UART_BRR_SAMPLING16(_PCLK_, _BAUD_) ((UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) << 4)|(UART_DIVFRAQ_SAMPLING16((_PCLK_), (_BAUD_)) & 0x0F))
#define UART_DIV_SAMPLING8(_PCLK_, _BAUD_) (((_PCLK_)*25)/(2*(_BAUD_)))
#define UART_DIVMANT_SAMPLING8(_PCLK_, _BAUD_) (UART_DIV_SAMPLING8((_PCLK_), (_BAUD_))/100)
#define UART_DIVFRAQ_SAMPLING8(_PCLK_, _BAUD_) (((UART_DIV_SAMPLING8((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING8((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)
#define UART_BRR_SAMPLING8(_PCLK_, _BAUD_) ((UART_DIVMANT_SAMPLING8((_PCLK_), (_BAUD_)) << 4)|(UART_DIVFRAQ_SAMPLING8((_PCLK_), (_BAUD_)) & 0x0F))

思路看起来清楚了一些,但换汤不换药。为了节省脑细胞就不做分析了。
注意UART_BRR_SAMPLING8还弄错了,最后的 0x0F 应该是 0x07

以下是我改写的STM32Cube版,自行替换:

1
2
3
4
5
6
#define UART_BRR_SAMPLING16(_PCLK_, _BAUD_) (((_PCLK_)+(_BAUD_)/2)/_BAUD_)
__INLINE static uint16_t UART_BRR_SAMPLING8(uint32_t _PCLK_, uint32_t _BAUD_)
{
uint16_t Div = (_PCLK_ + _BAUD_/2)/_BAUD_;
return ((Div & ~0x7)<<1 | (Div & 0x07));
}

后记

这个问题由来已久,略有强迫症的我每更新stdperiph一个版本就要看看这个问题改进了没有。新项目用F407,开始接触STM32CubeF4 1.5,发现了8超采样的bug,在ST官网论坛提了没人理,其后的1.6, 1.7版bug依旧。要不然也不费这个劲写这篇文章了。

文章目录
  1. 1. 前言
  2. 2. 波特率计算公式
  3. 3. 16倍采样下BRR的计算
  4. 4. 8倍超采样下BRR的计算
  5. 5. 库函数代码存在的问题
  6. 6. 后记