提示:此文章只是分析了一种优化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;
FILE* fp;
int err;
if ((err = fopen_s(&fp, "1.h", "w")) != 0)
{
printf("无法打开此文件\n");
exit(0);
}
for (f = 1; f < 100 * 1000; f++)
{
num = 72000000/ f;
num_temp = num;
Symbol_Opt = 0;
Number_Offset = 1;
while (1)
{
flag = 0;
for (i = 1; i < 65536; i++)
{
if (((num_temp / i) < 65536) && (num_temp % i == 0))
{
Psc = i;
Arr = num_temp / i;
Cycle = num_temp;
flag = 1;
break;
}
}
if (flag == 0)
{
if ((num <= (4294836225 - Number_Offset)) && (Symbol_Opt == 0))
{
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);
xx++;
if (xx == 10)
{
xx = 0;
fprintf(fp,"\\\n");
}
Error = ((72000000.0 / (Psc * Arr)) - f) / f;
}
fclose(fp);
printf("当mun为%8lu时,频率f为%8luHz时,最大误差:%f%%\n", ErrornumMax, ErrorfMax, ErrorMax*100.0);
??(为了方便,我将多个功能整合在上面一套代码当中,需要哪些功能自行分析,屏蔽代码便可实现)
最终结果如下: ??经发现,在频率值达到某一频率时,一直满足Psc=1的条件。这是由于频率的提高,周期的减少,使得周期已经可以在655361/72000000 s之内。也就是PSCARR<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字节。
|