搜索
bottom↓
回复: 11

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

[复制链接]

出0入0汤圆

发表于 2015-8-24 18:08:12 | 显示全部楼层 |阅读模式
本帖最后由 iskywolf 于 2015-8-24 18:24 编辑

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

波特率计算公式
STM32F1系列波特率计算只有一个公式:
Tx/Rx Buad=PCLK/(16∗USARTDIV)

F2之后的系列有两公式,增加了8倍超采样的模式,将16换成8就行。关于超采样,指接收端以高于波特率的时钟来采集接收波形,以减少因线路干扰带来的误码。

16倍超采样下BRR的计算
STM32的usart波特率生成支持小数分频,BRR寄存器高12位是整数部分,低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,程序里一般写(PCLK+Baud/2)/Baud。实际由于STM32已经做了小数分频,其实两种算法结果差不多。只有当外设时钟设置的比较低(例如要考虑节能原因),而所需波特率又比较高时,才有必要考虑舍入问题。不放心的话用可以方便地试算BRR和误差量。UART通迅波特率误差一般要求<2.5%,实际误差大多小于1%。下表是外设时钟36M为例计算常见的波特率:

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


8倍超采样下BRR的计算
至于8倍超采样的,跟16倍的一回事,只不过此时BRR寄存器小数位只用了3位,中间空了1个bit,要做一下移位。先按不移位计算:BRR=(PCLK/(8*Baud))<<3=PCLK/Baud,结果公式一模一样,然后将整数部分左移1位就行了。完整代码如下:
  1. uint16_t Div = PCLK/BAUD;  //也可写成(PCLK + BAUD/2)/BAUD  
  2. uint16_t DIV_Mantissa = (Div & ~0x7)<<1;
  3. uint16_t DIV_Fraction = Div & 0x07;
  4. BRR = DIV_Mantissa | DIV_Fraction;  
复制代码
8倍超采样对精度没啥影响,可以提高波特率的上限。例如上表中8M时钟,用16倍超采样,波特率最高只能做到500k。改成8倍超采样,波特率最高就可以到1M了。

库函数代码的问题
不管16倍还是8倍,换算公式都这么简单,不知道函数库里为什么弄那么复杂。stm32f10x_stdperiph_lib_3.1.x是这样写的
  1. void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
  2. {//....前面一段省略...
  3.   /* Determine the integer part */
  4.   integerdivider = ((0x19 * apbclock) / (0x04 * (USART_InitStruct->USART_BaudRate)));
  5.   tmpreg = (integerdivider / 0x64) << 0x04;
  6.   /* Determine the fractional part */
  7.   fractionaldivider = integerdivider - (0x64 * (tmpreg >> 0x04));
  8.   tmpreg |= ((((fractionaldivider * 0x10) + 0x32) / 0x64)) & ((uint8_t)0x0F);
  9.   /* Write to USART BRR */
  10.   USARTx->BRR = (uint16_t)tmpreg;
  11. }
复制代码

不知道你绕晕了没有,我是晕了。
stm32f10x_stdperiph_lib_3.4.0增加了8倍超采样的情况,一脉相承:
  1. void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
  2. {//....前面一段省略...,以下修改了一下缩进,要不太长了
  3.   /* Determine the integer part */
  4.   if ((USARTx->CR1 & CR1_OVER8_Set) != 0)  {
  5.     /* Integer part computing in case Oversampling mode is 8 Samples */
  6.     integerdivider = ((25 * apbclock) / (2 * (USART_InitStruct->USART_BaudRate)));   
  7.   }else { /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
  8.     /* Integer part computing in case Oversampling mode is 16 Samples */
  9.     integerdivider = ((25 * apbclock) / (4 * (USART_InitStruct->USART_BaudRate)));   
  10.   }
  11.   tmpreg = (integerdivider / 100) << 4;

  12.   /* Determine the fractional part */
  13.   fractionaldivider = integerdivider - (100 * (tmpreg >> 4));

  14.   /* Implement the fractional part in the register */
  15.   if ((USARTx->CR1 & CR1_OVER8_Set) != 0){
  16.     tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t)0x07);
  17.   }  else  {/* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
  18.      tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
  19.   }

  20.   /* Write to USART BRR */
  21.   USARTx->BRR = (uint16_t)tmpreg;
  22. }
复制代码

到了STM32Cube时代,换算公式改成了宏。以下是来自STM32Cube_FW_F4_V1.5.0。
  1. #define UART_DIV_SAMPLING16(_PCLK_, _BAUD_)            (((_PCLK_)*25)/(4*(_BAUD_)))
  2. #define UART_DIVMANT_SAMPLING16(_PCLK_, _BAUD_)        (UART_DIV_SAMPLING16((_PCLK_), (_BAUD_))/100)
  3. #define UART_DIVFRAQ_SAMPLING16(_PCLK_, _BAUD_)        (((UART_DIV_SAMPLING16((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)
  4. #define UART_BRR_SAMPLING16(_PCLK_, _BAUD_)            ((UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) << 4)|(UART_DIVFRAQ_SAMPLING16((_PCLK_), (_BAUD_)) & 0x0F))

  5. #define UART_DIV_SAMPLING8(_PCLK_, _BAUD_)             (((_PCLK_)*25)/(2*(_BAUD_)))
  6. #define UART_DIVMANT_SAMPLING8(_PCLK_, _BAUD_)         (UART_DIV_SAMPLING8((_PCLK_), (_BAUD_))/100)
  7. #define UART_DIVFRAQ_SAMPLING8(_PCLK_, _BAUD_)         (((UART_DIV_SAMPLING8((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING8((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)
  8. #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. #define UART_BRR_SAMPLING16(_PCLK_, _BAUD_)     (((_PCLK_)+(_BAUD_)/2)/_BAUD_)
  2. __INLINE static uint16_t UART_BRR_SAMPLING8(uint32_t _PCLK_, uint32_t _BAUD_)
  3. {
  4.     uint16_t Div = (_PCLK_ + _BAUD_/2)/_BAUD_;  
  5.     return ((Div & ~0x7)<<1 | (Div & 0x07));
  6. }  
复制代码


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2015-8-24 18:19:48 | 显示全部楼层
本帖最后由 iskywolf 于 2015-8-24 18:23 编辑

刚开始尝试用markdown写技术博客,刚写了一篇发在:http://skywolf.farbox.com/post/s ... u-ji-suan-de-wen-ti
想在这发一份,结果发现发贴不支持markdown?复制HTML过来格式都乱了套,重新编辑了好几次才弄好。

出0入0汤圆

发表于 2015-8-24 18:57:14 | 显示全部楼层
lz 果有强迫症,8倍超采字节问题这可能就是ST存心预留的小Bug。

出0入0汤圆

发表于 2015-8-24 21:11:55 | 显示全部楼层
谢谢分享!

出0入0汤圆

发表于 2015-8-24 21:36:44 | 显示全部楼层
  1. uint32_t USART_BRR( uint32_t clock, uint32_t baud )
  2. {
  3.   uint64_t clcok_x = clock * 10000ULL;
  4.   uint64_t brr = ( ( clcok_x / baud ) + 5000ULL ) / 10000ULL;
  5.   return brr;
  6. }
复制代码


网上找到一个算法, OVER8 = 0 : 16倍采样

出0入0汤圆

发表于 2015-8-24 21:49:48 | 显示全部楼层
STM32 USART波特率精确设置方法(修正ST库的损失精度的计算方法)
http://www.amobbs.com/thread-4984158-1-1.html
(出处: amoBBS 阿莫电子论坛)

出0入0汤圆

发表于 2015-8-24 21:51:18 | 显示全部楼层
一直有疑问,始终不得解~

出0入0汤圆

发表于 2015-8-24 21:58:46 | 显示全部楼层
Great++++++++++++++

出0入0汤圆

 楼主| 发表于 2015-8-24 22:24:46 | 显示全部楼层
本帖最后由 iskywolf 于 2015-8-25 10:36 编辑
shangdawei 发表于 2015-8-24 21:36
网上找到一个算法, OVER8 = 0 : 16倍采样


这个算法不可取,精度跟(PCLK + BAUD/2)/BAUD一样,但多了一次乘法,一次除法,而且都还是64位计算。
(PCLK + BAUD/2)/BAUD只需要一个移位,一个加法,一个除法。

出0入0汤圆

 楼主| 发表于 2015-8-24 22:29:07 | 显示全部楼层
本帖最后由 iskywolf 于 2015-8-25 08:51 编辑
shangdawei 发表于 2015-8-24 21:49
STM32 USART波特率精确设置方法(修正ST库的损失精度的计算方法)
http://www.amobbs.com/thread-4984158-1-1 ...

同样不可取,见楼上。不管乘多大数再除掉,精度不可能超过这个算法:(PCLK + BAUD/2)/BAUD,只不过增加运算量。
看来我的贴子没白写,居然暴露另外一个误区。精度能到多高,由BRR的4bit小数决定了,不管乘多大的数,最终一除还是4bit小数。
理论误差最大就是正负0.5bit了,也就是分频系数的1/32。如果要保证1%的精度,分频系数>100/32=3.125就够了,一般系统都可以满足。也就是要么分频系数够大,要么尽量整除,误差就可以满足要求。
如果硬要扯绝对精度,当BAUD为奇数时,除了那0.5bit的误差,还有因为BAUD/2除不尽再来的误差,也就是1/BAUD bit。那0.5bit的误差都可以忽略了,1/BAUD bit的误差就更不用提了吧?
而且有谁的系统里波特率是奇数?

PS:我们用的系统都是有限精度系统,只要精度满足要求就行了。

出0入0汤圆

发表于 2015-8-25 02:57:47 | 显示全部楼层
原来还有这么逗的事情…倒是觉得ST的代码库和文档质量不如TI高就是了,效率还不清楚

出0入0汤圆

发表于 2023-4-24 11:51:37 | 显示全部楼层
这个不错,谢谢
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-18 10:08

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表