提示:此文章只是分析了一种优化STM32发送脉冲减少误差的方法实现,由于本人水平有限,该方法并不是最优解,但确是一种比较容易理解的实现方法。

STM32输出1-500KHz任意整数频率脉冲,代码时间空间优化实现误差最小频率输出。

  • 前言
  • 一、问题及简化后的数学模型
  • 二、解决方法分析
  • 三、最终结果

前言

  在使用单片机发送脉冲时,往往要求发送范围比较广的任意频率的脉冲,在STM32当中实现指定频率脉冲的发送时,需要计算预分频重装载值,但是有些频率,可以由多个预分频和重装载值计算得出,有些频率无法通过预分频和重装载值计算得出,只能计算出与该频率误差最小的频率进行代替,并且由频率反推计算预分频和重装载值需要消耗CPU较多资源,如果提前将每个频率对应最小误差的预分频值和重装载值计算出来,确实可以减少输出频率的误差,并降低CPU的资源,但是往往实际应用过程当中需要输出的频率范围较广,这样得到的预分频值和重装载值将会非常庞大,本文章就此提出一种优化空间时间性能且便于理解,方便实现的方法。


  提示:以下是本篇文章正文内容

一、问题及简化后的数学模型

  在定时器时间基准固定为最大72MHz时,控制STM32输出脉冲的周期、频率取决于PSC(预分频)和ARR(重装载),有等式:PSC * ARR * F = 72 000 000,当在输入F确定时,可得等式:PSC * ARR = 72 000 000 / F,即PSC * ARR = 确定值。但是PSC和ARR在32单片机当中都是16位的寄存器,所以也就有了限制条件:0 < PSC < 65535 、 0 < ARR < 65535 。所以求PSC和ARR也就化简为一道数学题。
  已知0<psc<65535,0<arr<65535,0< f <72 * 1000 * 1000,在 psc 、 arr 和 f 均为整数,且 f 已定的前提下,求 psc 和 arr 的数值使得 psc * arr * f = 72 * 1000 * 1000 的误差最小。

二、解决方法分析

  在我们确定F的情况下,PSC * ARR = 72 000 000 / F 等同于PSC * ARR = T,T = 72 000 000 / F。由于PSC和ARR的限制条件(小于65536的自然数),导致PSC * ARR不一定等于T(比如T为大于65535的质数),但是,我们应该要减少由PSC和ARR得到的频率与所需求频率的误差,下面就是我的思想:

  首先判断PSC * ARR = T等式能否直接成立,通过T对范围内所有不同的PSC相除,判断是否余数为0,如果为0,说明存在两个整数相乘可以得到T,其中除于PSC后得到的值为ARR,但是同时需要注意ARR也有限制条件,只有满足:T能被在范围内的PSC整除,且整除结果ARR也在范围内,才能得到误差为0的PSC * ARR组合。(使用遍历的方法得到满足 PSC * ARR = T 公式的PSC和ARR)
  如果T没有符合条件的PSC * ARR组合,那可以先试试T+1(误差为1/(T+1))是否具有满足 PSC * ARR = T+1 公式的PSC和ARR,如果依然没有的话,再尝试一下T-1(误差为1/(T-1)),仍然没有的话,继续尝试T+2(误差为2/(T+2))、T-2(误差为2/(T-2))、T+3(误差为3/(T+3))…等方案,同时,还需要考虑到T+x确保在0 ~ 65535*65535的范围内(不在此范围内的T是永远得不到满足条件的PSC * ARR组合)。最终在满足这些条件下得出对应的PSC * ARR组合,而由此组合得到的单片机脉冲频率与所需频率误差最小(事实上由频率转成T往往存在小数,本方案忽略小数部分的误差)
  同时,也可以测量一下所有频率通过此方法计算出 PSC * ARR 组合所需的最长时间,和由计算出来的 PSC * ARR 组合生成的PWM的周期的最大误差,如下图所示(PWM输出范围为1Hz ~ 100KHz时):(源代码贴在后面)
  在Windows平台上的情况:

  (因为clock();精度以及运行环境的影响,实际运行速度可能稍微有点偏差,导致每次计算同一频率所花费的时间都不一样,但是不影响我们接下来的测试。)

  从图片当中可以看到,当 频率为11 Hz时,计算机最长需要时间0.001s便可以计算出结果

  而在STM32F103平台上,我们通过设置断点,测量转换时间最长频率在STM32F103平台下需要的时间:

  执行到断点一的时间:0.00018693

  执行到断点二的时间:9.00021275

  在STM32F103平台上,一次频率转换最长时间为9.00021275 - 0.00018693 = 9.00002582
  也就是9s多,对于单片机的来说,根本等不了9s时间只为得到一个误差最小的PSC * ARR~
  所以通过MCU自己将所有的频率转换为PSC * ARR组合,方案根本行不通
  那还可以采用以下方案:
    1.计算机计算好数值通过通信将PSC和ARR两个参传进去
    2.单片机自己提前将PSC和ARR保存在存储器当中,需要时在取出来
  但也只是将运算时间减少,但是误差依然减少不了。
  这相当于,在输出1Hz ~ 100KHz时,频率转换为PSC和ARR最长需要 9.00002582 s,最大误差达到0.138688%之大。
  对于第一种方案,需要计算机与单片机一直长期通信连接,通过上位机先算好PSC和ARR,在下发到传入单片机,而对于第二种方式需要先提前在计算机平台计算好所有的PSC和ARR数组,存入单片机当中,单片机执行程序时在需要的时候再取出PSC和ARR。但是第一种方式局限性太大,所以优先考虑第二种方式。
  在Windows平台下,我们可以将计算好的PSC和ARR按照C语言数组格式输出到.c文件当中,在数组加上全局,只读变量等关键词,花括号等符号后,便可以直接添加到STM32项目工程当中。
输出程序:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>int main()
{/*定义变量*/double Total_time,Total_timeMax=0.0;clock_t start, finish;double Error, ErrorMax=0.0;                        //误差unsigned long int num = 0, num_temp = 0, ErrornumMax, Total_timenumMax;unsigned char flag = 0, Symbol_Opt, Number_Offset;unsigned int i, f, ErrorfMax, Total_timefMax;int xx=0;unsigned short Psc;                  //对应周期下的预分配系数unsigned short Arr;                    //对应周期下的重装载值unsigned int Cycle;                 //STM32所有周期保存地址FILE* fp;//建立一个文件操作指针int err;if ((err = fopen_s(&fp, "1.h", "w")) != 0)//以追加的方式建立或打开1.txt,默认位置在你程序的目录下面{printf("无法打开此文件\n");            //如果打不开,就输出打不开exit(0);                               //终止程序}for (f = 1; f < 100 * 1000; f++)//j是频率:1 ~ 100k(100KHz){//start = clock();num = 72000000/ f;      //100K是720num_temp = num;Symbol_Opt = 0;Number_Offset = 1;while (1){flag = 0;for (i = 1; i < 65536; i++)//4294770690{if (((num_temp / i) < 65536) && (num_temp % i == 0))//满足条件说明有可以乘积的数值{//对其进行保存Psc = i;Arr = num_temp / i;Cycle = num_temp;flag = 1;break;}}if (flag == 0)//说明这个数没有了,该对num进行处理了{if ((num <= (4294836225 - Number_Offset)) && (Symbol_Opt == 0))// 65535 * 65535 = 4294836225{num_temp = num + Number_Offset;Symbol_Opt = 1;}else if ((num - Number_Offset > 0) && (Symbol_Opt == 1)){num_temp = num - Number_Offset;Symbol_Opt = 0;Number_Offset++;}}else{break;}}/*输出到文件当中*/fprintf(fp,"0x%X,0x%X,", Psc, Arr);//同输出printf一样,以格式方式输出到文本中xx++;if (xx == 10){xx = 0;fprintf(fp,"\\\n");//同输出printf一样,以格式方式输出到文本中}/*记录计算机计算时间*///finish = clock();//Total_time = (double)(finish - start) / CLOCKS_PER_SEC; //单位换算成秒//if (Total_time > Total_timeMax)//{//  Total_timefMax = f;//  Total_timeMax = Total_time;//  Total_timenumMax = num;//}Error = ((72000000.0 / (Psc * Arr)) - f) / f;//误差=(实际频率-理论频率)/理论频率。//if (Error > ErrorMax)//{//   ErrorfMax = f;//   ErrorMax = Error;//    ErrornumMax = num;//}//printf("f=%-8lu    num=%-8lu    %8lu = %5lu * %5lu",f, num, Cycle, Psc, Arr);//printf("    误差:%f%%\n", Error*100.0);}fclose(fp);//关闭流//printf("当mun为%8lu时,%8luHz,最长时间:%f\n", Total_timenumMax, Total_timefMax, Total_timeMax);printf("当mun为%8lu时,频率f为%8luHz时,最大误差:%f%%\n", ErrornumMax, ErrorfMax, ErrorMax*100.0);
/*
当mun为 6545454时,最长时间:0.001000
当mun为     721时,频率f为   99723Hz时,最大误差:0.138688%
*/

  (为了方便,我将多个功能整合在上面一套代码当中,需要哪些功能自行分析,屏蔽代码便可实现)

最终结果如下:

  经发现,在频率值达到某一频率时,一直满足Psc=1的条件。这是由于频率的提高,周期的减少,使得周期已经可以在65536 * 1/72000000 s之内。也就是PSC * ARR < 65536。

  所以为了平衡时间性能和空间性能,我们可以将F大于该特定值时,通过Psc=1求得ARR的数值,小于该特定值的频率通过数组将PSC和ARR存起来。这样,将会大大减少Psc=1数组的空间。
  首先求得最先得到Psc=1时的F,求得F结果如下:

  所以只需要将1Hz ~ 1098Hz的数组保存下来即可。结果如下:
  对比一下两文件大小区别:

  由于加上了 const 修饰词,数组将会存储在MCU的 nor flash 当中,数组二维长度为1098,一维长度为2,元素类型为unsigned short:2字节,所以占用空间大小为:109822=4392 Byte,4K左右,连51片内都有这么大空间的Flash,在STM32当中更能存储下这些字节。
  在程序当中只需要判断频率大于1098Hz便可以通过快速计算得出ARR和PSC,而在1Hz ~ 1098Hz之间,直接通过计算需要耗费较长时间和CPU资源,所以通过取数组的方式得到ARR和PSC。

代码如下:

void Frequency_Change_PSC_And_ARR(u32 Frequency,u16* psc,u16* arr)
{if(1<=Frequency && Frequency<=100000){if (Frequency<=1098){*psc=ARRPSC[Frequency-1][0];*arr=ARRPSC[Frequency-1][1];}else{*psc=1;*arr=72000000/((*psc)*Frequency);}}else{printf("输入频率不在1~100k之间。\n");}
}

三、最终结果

  续~~~增加100K~500KHZ频率
  由于摒弃了通过单片机自行计算出PSC和ARR的方案,所以我们目前可以不用考虑“为了得出PSC和ARR占用单片机太多CPU资源”这个问题,但是依然要考虑输出脉冲误差的问题,当输出频率较低时(1Hz ~ 1098Hz),也就是周期较高时,100KHz和500KHz没啥区别,当输出频率较高时,令Psc=1,单片机只需计算ARR,不需要耗费太多CPU资源,便可以节省高频对应的PSC和ARR数组空间。
  但是频率的提升,也会增大输出脉冲频率的误差。
如下图:



  从中可以看出100KHz脉冲和500KHz脉冲输出频率的误差相差不大,而且小到可以忽略(500KHz脉冲误差0.0069多),(虽然频率误差较小,但是频率大起来了,误差的脉冲数量就比较大)但是实际情况还得考虑对脉冲操作(如:输出方向反转,改变脉冲发送通道)等情况的延时对与输出脉冲的影响,F1系列最高才72MHz的主频,F4系列最高168MHz的主频,但是他们都是16位的定时器,使用F4后,相较于F1的低频(1Hz ~ 1098Hz)需要提前保存的数组10984字节,变成25634字节。

STM32输出1-500KHz任意整数频率脉冲,代码时间空间优化实现误差最小频率输出。相关推荐

  1. Verilog实现---1/x任意整数分频器通用代码

    目录 1.偶数分频 2.奇数分频 3.代码文件说明 4.端口说明 5.测试&波形 6.代码 7.Reference 1.偶数分频 对于占空比为50%,分频系数为N的偶数分频,其核心思想是使用计 ...

  2. Python代码发现链表中的环并输出环中的第一个元素

    Python代码发现链表中的环并输出环中的第一个元素 # Python代码发现链表中的环并输出环中的第一个元素 # Find first node of loop in a linked list # ...

  3. 咚咚咚————【封装驱动】Si5351A方波信号发生器发送任意(8K-160Mhz)频率程序

    咚咚咚----[封装驱动]Si5351A方波信号发生器发送任意[8K-160Mhz]频率程序 (一)效果展示 (二)源码分享 (三)需要改进的地方及不足 (使用阿波罗STM32F7开发板) (一)效果 ...

  4. matlab x为整数,关于matlab中用什么字符表示任意整数

    matlab做除法,怎么取整数? 方法一: floor(a/b);就是舍去小数点. ceil(a/b)就是舍去小数点+1的数. 方法二: fix(x)截尾取整 fix(x)不超过x的最大整数 ceil ...

  5. java 五个数字_关于java:五个任意整数找出其中第二大的数字

    package comxaqf.w02_objectoriented.a_saturday1030; import java.util.Scanner; /** [题6] 6.五个任意整数,找出其中第 ...

  6. C语言随笔小算法:取出一个任意整数的每一位数值

    C语言随笔小算法:取出一个任意整数的每一位数值 代码: #include "stdlib.h" #include "stdio.h"//将val的各位取出来 i ...

  7. python求1到n的奇数和_编写程序。输入任意整数n,计算1到n的奇数和。C语言编写程序 输入整数N 显...

    编写程序.输入任意整数n,计算1到n的奇数和. C语言编写程序 输入整数N 显 www.zhiqu.org 时间: 2020-11-23 解题思路:循环判断1到N的每一个数: 若除以2是结果为整数,也 ...

  8. Python 输入任意整数,打印输入的数字是几位数

        Python 输入任意整数,打印输入的数字是几位数   根据题目,这个比较简单,但有很多人会想的比较复杂,复杂在键盘输入0开始,后面接任意个零都可以,那么,比如,输入00024,这个输入是没有 ...

  9. 任意整数有几种分解方法 java_整数的分解方法

    腾讯 2017春招真题 题目 如下示例: 1:共0种分解方法: 2:共0种分解方法: 3:3=2+1 共1种分解方法: 4:4=3+1=2+1+1 共2种分解方法: 5:5=4+1=3+2=3+1+1 ...

最新文章

  1. 左神讲算法——二分法及其拓展
  2. 基类的析构函数不能被继承。_为什么要把C++类中的析构函数声明为虚函数?
  3. linux开启内部路由转发功能
  4. 三因素三水平正交表l9_影响多腔导管挤出机头设计的关键因素
  5. AHK-UMSS框架 (AHK通用修饰键解决方案,任何键都是修饰键)
  6. 无符号右移负数_关于负数的右移与无符号右移运算小结
  7. linux下sqlmap安装教程,(转)Sqlmap官网下载与安装教程[windows/linux版本]
  8. java复制sheet_Java对excel中的sheet进行拷贝
  9. word2010赠送_我们将赠送两台LulzBot 3D打印机
  10. app嵌入jsp页面的项目工作量_好程序员Java学习路线分享jsp为什么用的不多了
  11. opencv打开的图片应用于nn.Conv2d()(二)
  12. Disruptor内存消息队列简单使用
  13. 机器学习的训练数据(Training Dataset)、测试数据(Testing Dataset)和验证数据(Validation Dataset)
  14. MVC通过重写OnActionExecuting获取控制器,方法和域
  15. php ini set开启方法,php ini_set更改php.ini配置功能_PHP教程
  16. NS方程求解-NSFnet
  17. python离线安装环境 解决 ERROR: Could not find a version that satisfies the requirement xxx 以及winError[10061]
  18. L337. 打家劫舍 III
  19. 银行对公业务数字化迎来新机遇
  20. GCC 预处理的宏 (predefined macros)

热门文章

  1. 【题解】nkoj 9061 通用的0
  2. AD layout完成后如何对板框进行修改
  3. PADS Layout制板文件和贴片文件的输出方法
  4. 大连理工计算机管理专业硕士,2018年大连理工大学硕士研究生招生专业一览表...
  5. 生产环境慎用 redis 模糊匹配功能!
  6. 如何做好一份程序员的工作汇报ppt?
  7. google金山词霸推出挑战有道桌面词典
  8. mysql数据库引擎转换_[转]MySQL数据库引擎
  9. c++蛮力法例子_学习方法系列丨贝叶斯学习法,我们几乎每天都在使用的数学工具...
  10. 圆角按钮css,基于CSS3的一组圆角按钮 - YangJunwei