STM32输出1-500KHz任意整数频率脉冲,代码时间空间优化实现误差最小频率输出。
提示:此文章只是分析了一种优化STM32发送脉冲减少误差的方法实现,由于本人水平有限,该方法并不是最优解,但确是一种比较容易理解的实现方法。STM32输出1-500KHz任意整数频率脉冲,代码时间空间优化实现。前言一、问题及简化后的数学模型二、解决方法分析三、最终结果前言 在使用单片机发送脉冲时,往往要求发送范围比较广的任意频率的脉冲,在STM32当中实现指定频率脉冲的发送时,需要计算预分频和重
提示:此文章只是分析了一种优化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是720
num_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字节。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)