|
本文想借此表达一下个人的一个观点,做开发如果遇到无法解决的难题,可以试着从数学的角度出发,看能否找到答案。 注:文中配图只为阅读轻松一点,本人数学也是半吊子,有错误帮忙指正。
6 l* Z& I; J4 H 6 X3 x6 q, u j% K- D3 e
那么要怎么判断一个物理量的变化趋势呢?我们能自然能想到去求取该随机序列的变化率。这里涉及到一些数序定义。随机序列有很多可能的来源,最为常见是我之前在<<模数转换知多少>>中介绍的模数采样。 这样将S(t)信号转换为离散信号序列S(n),那么对于当前时刻其斜率怎么求取呢?(这里忽略中间的过度态,仅将其看为线段相连,当然现实应用中如果有更高要求,可以做曲线拟合)
但是如果只判断,斜率极容易误判,比如下面这样的情况:
! x; }1 D6 a- i c P! P 凹函数是一个定义在某个向量空间的凸集C(区间)上的实值函数f。设f为定义在区间I上的函数,若对I上的任意两点x1<x2和任意的实数t属于(0,1),总有,
则称函数f为l上凹函数,有的书上也称为下凸函数。
如果把上述条件中的“≥”改成“>”,则叫做严格上凹函数,或叫做严格下凸函数。, A/ j0 s2 v' V3 L/ Y1 Y) v1 A% c 上面是一维函数情况,这里来个2维函数的图,刚方便理解
设f为定义在区间I上的函数,若对I上的任意两点x1<x2和任意的实数t属于(0,1),上面不等式变成大于等于,则在该区间为凸函数。
可见,凹凸是相对的,如f(x)在某区间为凹,则-f(x)则在该区间为凸。 性质
证明,这里就不推导了,可以利用拉格朗日中值定理可以推导出上面这个性质。 来看一下会动的图,加深一下理解:
sin(2x)的一阶导数为:
sin(2x)的二阶导数为:
装逼结束,也可能没装对~~~
通过上面装逼,是否可以利用离散序列的求导数来判断传感器的变化趋势。啥?导数?又要开始表演了?
前面说了一阶导数是这样的:
那么二阶导数是哪样捏?
化简一下:
其中S[n]表示当前测量点,S[n-1]表示前一个测量点,S[n-2]表示前第2个测量点。应为+S[n-2]
#include <stdio.h>6 X( p0 D* U. ?3 Z% J2 a+ w$ Y. ~ #include <math.h>6 _8 }- b& h" i# l9 O" c+ C #include <string.h> typedef struct _T_2ND_DRV {9 c1 E% I7 [7 t, ~2 p1 M float xn1;& K8 }1 v' R V5 O float xn2;1 q ^. b: G$ | }t_2ND_DRV; typedef struct _T_1ST_DRV {9 P( n& w6 c z! s2 c- A float xn1; }t_1ST_DRV; void init_second_derivative(t_2ND_DRV *pSndDrv)! h& J' ^4 N& I) s {" k5 G! E9 p0 S8 [ O pSndDrv->xn1 = 0;6 q& w% U, i3 C pSndDrv->xn2 = 0;5 A1 A! E- n% s$ z% X" F/ a } float second_derivative(t_2ND_DRV *pSndDrv, float xn,float T) { float result=0.0f;4 ?! [' x7 i3 s1 [ if(T<=0) return 0x7FBFFFFF; /*非法数据*/; }1 A4 o3 a6 M4 x. ]9 R" A, E! { result = (xn-2*pSndDrv->xn1+pSndDrv->xn2)/T/T; pSndDrv->xn2 = pSndDrv->xn1; pSndDrv->xn1 = xn; return result; } 5 p5 I1 u. K! m% P, w0 h void init_fisrt_derivative(t_1ST_DRV *p1stDrv)8 I" @- [5 W" N$ s% x {9 c. |% Q6 a4 r p1stDrv->xn1 = 0; }. |+ r3 {' j8 M- F6 _) [7 W/ F( o0 @ ; ^' z g3 }0 h; y" W' K' X _( u float fisrt_derivative(t_1ST_DRV *p1stDrv, float xn,float T) { float result=0.0f; if(T<=0); u5 O3 ~, H1 E t0 X5 Q return 0x7FBFFFFF; /*非法数据*/ result = (xn-p1stDrv->xn1)/T; 3 Z# s2 R- M* J0 d" h+ t+ U p1stDrv->xn1 = xn;' I8 t5 J' I1 r* h- | " _5 e% T! h7 z3 ^! s# d7 x$ Y! H return result; } #define PI 3.1415f' w- s; J6 a, Q2 b" w3 q #define SAMPLE_RATE 500.0f #define SAMPLE_T (1/SAMPLE_RATE) #define SAMPLE_SIZE (100)8 w$ t$ h. T5 X' L( V4 y int main()! C- n1 t% ~- p! i/ Y { ~. _( j) L2 z' U+ ?8 q float sim1[SAMPLE_SIZE];) G1 O+ l! k. l5 @ float sim2[SAMPLE_SIZE]; float out1[SAMPLE_SIZE]; float out2[SAMPLE_SIZE]; t_2ND_DRV sndDrv;; a4 H! ~3 G* k% e t_1ST_DRV frtDrv;! P) S& i8 K# [2 J4 z init_fisrt_derivative(&frtDrv);. u- W) H, o+ S) a$ Z init_second_derivative(&sndDrv); FILE *pFile=fopen("./simulationSin.csv","wt+");) p7 L& d" V0 T+ R; I, m' d0 S if(pFile==NULL) {# {) x6 c+ L$ j; ?: A printf("simulationSin.csv opened failed"); return -1; }# j3 \, F4 i! V* m0 ]2 V for(int i=0;i<SAMPLE_SIZE;i++)6 f6 z9 j, s$ Q3 Q# h { sim1=10*sin(2*PI*10*i/500);* t+ E9 d, s1 E; l3 T5 P6 I# n& D } 0 z9 v3 W+ w& e. L; K1 M for(int i=0;i<SAMPLE_SIZE;i++) {+ m0 ]% { W# K4 {" T& [; P$ o' v+ J out1=fisrt_derivative(&frtDrv,sim1,SAMPLE_T); out2=second_derivative(&sndDrv,sim1,SAMPLE_T);2 w2 E% ?' n" t7 G! N+ W# @, q, o2 W fprintf(pFile,"%f,%f,%f\n",sim1,out1,out2);# z$ W3 h$ V1 M0 C. k) w3 i } fclose(pFile); , T0 E" K7 `0 V. |& T I" y return 0; } 忽略前两个点,利用excel生成曲线:
再进一步: 一阶导数与二阶导数结合起来看,就可以看出测量值变化趋势的趋势,比如在前1/4周期,此区间变换趋势为增,也即一阶导数为正,而其二阶导数为负,也可以看出递增的趋势是逐渐减小到0的。 代码优化如果只是做定性判断,上述函数,完全没必要与采样周期做除法,只需要考察其增量即可,代码可优化如下: typedef struct _T_2ND_DRV: R% ^3 r2 V( ]* }6 ^( l) u9 ^{ float xn1; float xn2; }t_2ND_DRV;) ~2 X- m0 ?3 f# { typedef struct _T_1ST_DRV { float xn1; }t_1ST_DRV; : {$ @3 u) v) M2 p9 w" V# w* F' q void init_second_derivative(t_2ND_DRV *pSndDrv)7 N6 ]: ?4 M4 h- t& i { pSndDrv->xn1 = 0; pSndDrv->xn2 = 0; }5 G, ^8 Y; ^4 T6 B7 G & U: L6 p4 W* N9 W float second_derivative(t_2ND_DRV *pSndDrv, float xn) {& B# U6 @2 E0 Z- P. ^, T8 C float result=0.0f;9 A4 p y9 Z8 E8 L8 w result = xn-2*pSndDrv->xn1+pSndDrv->xn2;; ~. e# Z: b; [# P pSndDrv->xn2 = pSndDrv->xn1;# ]) t. R8 m5 k! j/ F pSndDrv->xn1 = xn; - P, Z: ]0 L, X: V) W- Q! P return result; } # G) A* o% O3 g! P2 V) d+ v: B void init_fisrt_derivative(t_1ST_DRV *p1stDrv)* w6 S/ }0 W* `: _5 B { p1stDrv->xn1 = 0;- h: Q* O# ~) F8 _5 z0 L }3 j5 t- s6 R+ R0 V + ]& g9 }# W# Y6 I9 D float fisrt_derivative(t_1ST_DRV *p1stDrv, float xn) { float result=0.0f;" ]; P3 l; i3 S+ |1 T! x- i8 h8 X result = xn-p1stDrv->xn1; : J( s& {' Q8 u& n1 g1 P p1stDrv->xn1 = xn; return result; }3 ?1 l3 M& F& r6 ?" \9 C 意外收获 这里意外引入一个可能很多人没注意的知识点NaN,在计算中,NaN代表非数字,是数字数据类型的成员,可以将其解释为不确定的或无法表示的值,尤其是在浮点运算中。1985年,IEEE 754浮点标准引入了NaN的系统使用,并表示了其他无限量(如无穷大)。 前述函数返回0x7FBFFFFF,也就是表示无穷大。 不同的操作系统和编程语言可能具有NaN的不同字符串表示形式: nan( f; h3 m9 P5 _NaN NaN%) d& r6 P2 A0 S NAN NaNQ+ j1 d( i& h8 e. T) U4 ~ NaNS. p5 H3 `9 a. p8 H! g' p qNaN: F9 ?/ C/ h7 O% C6 r* a sNaN) k& |) l- \2 [" ]2 P& Y 1.#SNAN 1.#QNAN+ D% u0 ]' M1 @8 `/ O. G `4 D1 @ -1.#IND7 ], `7 g& X5 g% j1 u 实际上,由于编码的NaN具有符号,因此通常也可以在NaN的字符串表示中找到它们,例如: -NaN# _8 t8 C1 _9 t3 A+ xNaN12345, E& P1 q( P$ _; p, q3 h -sNaN123009 k. N7 Y* I8 ]# r3 Z4 \& k) ]% ^' ] -NaN(s1234)6 a" @5 U0 g$ w/ L) t3 [ 工程应用 这里给出我的建议方案:
将传感器信号经由电路处理,模数采样,在进入前级数字滤波器,滤除不必要的噪声,在进行一阶/二阶求导。对于一阶和二阶求导再做一级移动平均滤波,最后在按照上面描述进行判别变化趋势,则个人认为基本就比较健壮了。实际移动均值滤波长度不宜选择过长,否则响应就比较滞后了。不能对传感器的变化趋势做出实时的判别。加了后级均值滤波器,则会消除由于波形忽上忽下的随机噪声干扰影响,使得系统判别更为健壮,实际滤波器长度需根据不同的场合进行调试优化。或者也可以选择别的IIR/FIR滤波器形式实现。 具体实现可参考(点击可阅读): [color=var(--weui-LINK)]手把手教系列之移动平均滤波器C实现 [color=var(--weui-LINK)]手把手教系列之IIR数字滤波器设计实现 做为嵌入式er编程,有时候有必要去看看数学书,了解一下数学原理的背后故事,可能会给你带来意想不到的作用哦。 |
| 谢谢分享 |
微信公众号
手机版