×
单片机 > 单片机程序设计 > 详情

STM32库函数USART波特率计算的问题

发布时间:2020-08-26 发布时间:
|
STM32的串口波特率计算本来没多大个事,只不过ST的StdPeriph以及后继者STM32Cube计算波特率那块弄得很复杂。写此文的目的是避免新手在这一块被函数库误导了。

 

STM32F1波特率计算只有一个公式:

F2之后的系列有两公式,增加了8倍采样的模式,将16换成8就行。

 

先说常用的16倍采样。STM32的USART波特率生成支持小数分频,BRR寄存器高12位是整数部分,低4位是小数部分。刚好有4位小数部分,于是:

BRR=(PCLK/(16*Baud))<<4=PCLK/Baud,so easy!你看硬件设计师都设计好了,波特率计算就这么简单。这样分频系数截尾误差最大1个bit,4位小数也就是1/16=0.0625,

更精确一点计算公式就考虑舍入,BRR=(PCLK+Baud/2)/Baud,分频系数的舍入误差最大0.5bit,也就是0.03125。

 

 题外话:有些新手可能有点迷糊,其实用定点整形来计算小数都是这样的思路,BRR就是个带4bit小数的定点数而已。由于定点处理器算浮点数很慢,需要提高计算速度时,就要约定小数位数然后跟整数一样计算。做加减还好,小数点不会变,做乘除就得弄清楚每一步小数点跑哪去了。。。

 

以下讨论里用简化的PCLK/Baud,程序里按需要选择。实际由于STM32已经做了小数分频,只有当外设时钟设置的比较低(例如节能原因),而所需波特率又比较高时,才有必要考虑舍入问题。

不放心的话用附件里的excel表格可以方便地试算BRR和误差量(一般要求<2.5%,实际误差大多小于1%)。

以下两个表格按是F1常用的36M为例计算常见的波特率。

以下是8M外设时钟计算常见波特率:

 

至于8倍过采样的,跟16倍的一回事,只不过此时BRR寄存器小数位只用了3位,中间空了1个bit,要做一下移位。

先按不移位计算:

BRR=(PCLK/(8*Baud))<<3=PCLK/Baud,结果公式一模一样,然后将整数部分左移1位就行了。完整代码如下:


uint16_t Div = PCLK/BAUD; //也可写成(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是这样写的:


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倍过采样的情况,一脉相承:


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。


#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还弄错了,本是0x07写成了0x0F。

 

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


#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));
}


题外话:

这个问题由来已久,略有强迫症的我每更新库函数一个版本就要看看这个问题改进了没有。从F4开始用STM32Cube,发现了8倍采样的bug,在ST官网论坛提了没人理,其后的1.5, 1.6, 1.7版bug依旧。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
浅谈AVR中定时器几种工作模式