你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

嵌入式编程凹凸性之妙用

[复制链接]
gaosmile 发布时间:2020-8-11 15:19
是个啥坑?

一个项目中用到一个传感器测量一物理量,这里假定测量温度吧。需要判断其变化趋势,利用这个变化趋势去做一些应用。
那么要怎么判断一个物理量的变化趋势呢?我们能自然能想到去求取该随机序列的变化率。这里涉及到一些数序定义。随机序列有很多可能的来源,最为常见是我之前在<<模数采样知多少>>中介绍的模数采样。
这样将S(t)信号转换为离散信号序列S(n),那么对于当前时刻其斜率怎么求取呢?(这里忽略中间的过度态,仅将其看为线段相连,当然现实应用中如果有更高要求,可以做曲线拟合)
1597129204(1).png

但是如果只判断,斜率极容易误判,比如下面这样的情况:
微信图片_20200811145243.png
其斜率一会儿正,一会儿负,但是其总体趋势又是在增加的,所以只考察斜率显然不可取,获取需要在代码在加各种复杂的条件或者限值去判断。即使加这么多条件系统仍然可能表现的非常不健壮。
微信图片_20200811145246.png
对于模拟信号2而言,趋势又在不断变化。那么怎么做才能稳定呢?先卖个关子?
微信图片_20200811145249.jpg
函数的凹凸性

凹函数

凹函数是一个定义在某个向量空间的凸集C(区间)上的实值函数f。设f为定义在区间I上的函数,若对I上的任意两点x1<x2和任意的实数t属于(0,1),总有,
1597129285(1).png
则称函数f为l上凹函数,有的书上也称为下凸函数。
微信图片_20200811145252.png
如果把上述条件中的“≥”改成“>”,则叫做严格上凹函数,或叫做严格下凸函数。
上面是一维函数情况,这里来个2维函数的图,方便理解。

1597129325(1).png
微信图片_20200811145255.png


凸函数

设f为定义在区间I上的函数,若对I上的任意两点x1<x2和任意的实数t属于(0,1),上面不等式变成大于等于,则在该区间为凸函数。
微信图片_20200811145258.png
可见,凹凸是相对的,如f(x)在某区间为凹,则-f(x)则在该区间为凸。
性质

若一个函数在某区间二阶可导且大于0,则函数在该区间为凹函数
若一个函数在某区间二阶可导且小于0,则函数在该区间为凸函数
证明,这里就不推导了,可以利用拉格朗日中值定理可以推导出上面这个性质。
来看一下会动的图,加深一下理解:
微信图片_20200811145301.gif
函数 1597130031(1).png 切线为蓝色,曲线向上凹,绿色表示曲线是向下凹的,红色表示曲线的拐点。
sin(2x)的一阶导数为: 1597130096(1).png
sin(2x)的二阶导数为: 1597130119(1).png

装逼结束,也可能没装对~~~
微信图片_20200811145304.jpg
回到坑里

通过上面装逼,是否可以利用离散序列的求导数来判断传感器的变化趋势。啥?导数?又要开始表演了?
微信图片_20200811145307.png
前面说了一阶导数是这样的: 1597130198(1).png
那么二阶导数是哪样捏?
1597130247(1).png
化简一下:
微信图片_20200811145310.png
其中S[n]表示当前测量点,S[n-1]表示前一个测量点,S[n-2]表示前第2个测量点。
上代码

#include <stdio.h>
#include <math.h>
#include <string.h>
typedef struct _T_2ND_DRV
{
    float xn1;
    float xn2;
}t_2ND_DRV;
typedef struct _T_1ST_DRV
{
    float xn1;
}t_1ST_DRV;

void init_second_derivative(t_2ND_DRV *pSndDrv)
{
    pSndDrv->xn1 = 0;
    pSndDrv->xn2 = 0;
}

float second_derivative(t_2ND_DRV *pSndDrv, float xn,float T)
{
     float result=0.0f;
     if(T<=0)
         return 0x7FBFFFFF; /*非法数据*/
     result = (xn-2*pSndDrv->xn1+pSndDrv->xn2)/T/T;
     pSndDrv->xn2 = pSndDrv->xn1;
     pSndDrv->xn1 = xn;
   
     return result;
}

void init_fisrt_derivative(t_1ST_DRV *p1stDrv)
{
    p1stDrv->xn1 = 0;
}

float fisrt_derivative(t_1ST_DRV *p1stDrv, float xn,float T)
{
     float result=0.0f;
     if(T<=0)
         return 0x7FBFFFFF; /*非法数据*/
     result = (xn-p1stDrv->xn1)/T;
     p1stDrv->xn1 = xn;
   
     return result;
}
#define PI 3.1415f
#define SAMPLE_RATE 500.0f
#define SAMPLE_T (1/SAMPLE_RATE)
#define SAMPLE_SIZE (100)
int main()
{
    float sim1[SAMPLE_SIZE];
    float sim2[SAMPLE_SIZE];
    float out1[SAMPLE_SIZE];
    float out2[SAMPLE_SIZE];
    t_2ND_DRV sndDrv;
    t_1ST_DRV frtDrv;
    init_fisrt_derivative(&frtDrv);
    init_second_derivative(&sndDrv);
   
    FILE *pFile=fopen("./simulationSin.csv","wt+");
    if(pFile==NULL)
    {
        printf("simulationSin.csv opened failed");
        return -1;
    }
   
    for(int i=0;i<SAMPLE_SIZE;i++)
    {
        sim1=10*sin(2*PI*10*i/500);
    }
    for(int i=0;i<SAMPLE_SIZE;i++)
    {
        out1=fisrt_derivative(&frtDrv,sim1,SAMPLE_T);
        out2=second_derivative(&sndDrv,sim1,SAMPLE_T);
        fprintf(pFile,"%f,%f,%f\n",sim1,out1,out2);
    }

    fclose(pFile);

    return 0;
}
忽略前两个点,利用excel生成曲线:
微信图片_20200811145314.png
从图中可看出:
一阶导数为正时,函数递增趋势;
一阶导数为负时,函数递减趋势;
二阶导数为0时,出现拐点,趋势改变;此时如果左右两侧的一阶导符号相反,则出现极值。
二阶导数为负时,其一阶导数也即原函数斜率规律单调减,二阶导数为正时,其一阶导数也即原函数斜率规律单调增。
再进一步:
一阶导数与二阶导数结合起来看,就可以看出测量值变化趋势的趋势,比如在前1/4周期,此区间变换趋势为增,也即一阶导数为正,而其二阶导数为负,也可以看出递增的趋势是逐渐减小到0的。
代码优化

如果只是做定性判断,上述函数,完全没必要与采样周期做除法,只需要考察其增量即可,代码可优化如下:
typedef struct _T_2ND_DRV
{
    float xn1;
    float xn2;
}t_2ND_DRV;
typedef struct _T_1ST_DRV
{
    float xn1;
}t_1ST_DRV;

void init_second_derivative(t_2ND_DRV *pSndDrv)
{
    pSndDrv->xn1 = 0;
    pSndDrv->xn2 = 0;
}

float second_derivative(t_2ND_DRV *pSndDrv, float xn)
{
     float result=0.0f;
     result = xn-2*pSndDrv->xn1+pSndDrv->xn2;
     pSndDrv->xn2 = pSndDrv->xn1;
     pSndDrv->xn1 = xn;
   
     return result;
}

void init_fisrt_derivative(t_1ST_DRV *p1stDrv)
{
    p1stDrv->xn1 = 0;
}

float fisrt_derivative(t_1ST_DRV *p1stDrv, float xn)
{
     float result=0.0f;
     result = xn-p1stDrv->xn1;
     p1stDrv->xn1 = xn;
   
     return result;
}
意外收获

这里意外引入一个可能很多人没注意的知识点NaN,在计算中,NaN代表非数字,是数字数据类型的成员,可以将其解释为不确定的或无法表示的值,尤其是在浮点运算中。1985年,IEEE 754浮点标准引入了NaN的系统使用,并表示了其他无限量(如无穷大)。
前述函数返回0x7FBFFFFF,也就是表示无穷大。
不同的操作系统和编程语言可能具有NaN的不同字符串表示形式:
nan
NaN
NaN%
NAN
NaNQ
NaNS
qNaN
sNaN
1.#SNAN
1.#QNAN
-1.#IND
实际上,由于编码的NaN具有符号,因此通常也可以在NaN的字符串表示中找到它们,例如:
-NaN
  NaN12345
-sNaN12300
-NaN(s1234)
工程应用

这里给出我的建议方案:
微信图片_20200811145317.png
将传感器信号经由电路处理,模数采样,在进入前级数字滤波器,滤除不必要的噪声,在进行一阶/二阶求导。对于一阶和二阶求导再做一级移动平均滤波,最后在按照上面描述进行判别变化趋势,基本比较健壮了。实际移动均值滤波长度不宜选择过长,否则响应就比较滞后了。不能对传感器的变化趋势做出实时的判别。加了后级均值滤波器,则会消除由于波形忽上忽下的随机噪声干扰影响,使得系统判别更为健壮,实际滤波器长度需根据不同的场合进行调试优化。或者也可以选择别的IIR/FIR滤波器形式实现。
总结一下

做为嵌入式er编程,有时候有必要去看看数学书,了解一下数学原理的背后故事,可能会给你带来意想不到的作用哦。
收藏 4 评论4 发布时间:2020-8-11 15:19

举报

4个回答
乐天乐 回答时间:2020-8-11 15:49:29
求导怎么计算?太高深了
超級稻草人 回答时间:2020-8-11 19:29:50
高手啊,在测试校准中要用到这些知识吧?
期望能够分享更多的知识!
我是胡工 回答时间:2020-8-12 12:50:11
这是在干啥?
sunj306 回答时间:2020-8-16 17:11:48
太高深 了

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版