单片机算法_单片机 汇编 算法 - CSDN
  • 源:经典算法大全(51个C语言算法+单片机常用算法+机器学十大算法 转载于:https://www.cnblogs.com/LittleTiger/p/10323059.html

    源:经典算法大全(51个C语言算法+单片机常用算法+机器学十大算法

    转载于:https://www.cnblogs.com/LittleTiger/p/10323059.html

    展开全文
  • 单片机C语言常用算法

    2020-07-23 23:31:37
    单片机C语言常用算法
  • 包含单片机常用数据结构+部分算法。顺序表丶链表、双向循环链表、队列链表存储、循环队列均有。算法包括,一个串口缓冲算法,可以解析帧头帧尾,固定数据长度的数据包。另一个是归排序,都已经验证通过。
  • 单片机常用算法

    2018-03-22 08:53:13
    算法的描述:是对要解决一个问题或要完成一项任务所采取的方法和步骤的描述,包括需要什么数据(输入什么数据、输出什么结果)、采用什么结构、使用什么语句以及如何安排这些语句等。通常使用自然语言、结构化流程.....

    本文转载自:http://home.eeworld.com.cn/my/space-uid-554974-blogid-255035.html

    算法(Algorithm):计算机解题的基本思想方法和步骤。
    算法的描述:是对要解决一个问题或要完成一项任务所采取的方法和步骤的描述,包括需要什么数据(输入什么数据、输出什么结果)、采用什么结构、使用什么语句以及如何安排这些语句等。通常使用自然语言、结构化流程图、伪代码等来描述算法。

    一、计数、求和、求阶乘等简单算法
      此类问题都要使用循环,要注意根据问题确定循环变量的初值、终值或结束条件,更要注意用来表示计数、和、阶乘的变量的初值。
      例:用随机函数产生100个[0,99]范围内的随机整数,统计个位上的数字分别为1,2,3,4,5,6,7,8,9,0的数的个数并打印出来。
      本题使用数组来处理,用数组a[100]存放产生的确100个随机整数,数组x[10]来存放个位上的数字分别为1,2,3,4,5,6,7,8,9,0的数的个数。即个位是1的个数存放在x[1]中,个位是2的个数存放在x[2]中,……个位是0的个数存放在x[10]。
    void main()
    {
    int a[101],x[11],i,p;
    for(i=0;i<=11;i++)
    x=0;
    for(i=1;i<=100;i++)
    {
    a=rand() % 100;
    printf("%4d",a);
    if(i%10==0)printf("\n");
    }
    for(i=1;i<=100;i++)
    {
    p="a"%10;
    if(p==0) p="10";
    x[p]=x[p]+1;
    }
    for(i=1;i<=10;i++)
    {
    p="i";
    if(i==10) p="0";
    printf("%d,%d\n",p,x);
    }
    printf("\n");
    }

    二、求两个整数的最大公约数、最小公倍数
      分析:求最大公约数的算法思想:(最小公倍数=两个整数之积/最大公约数)
    (1) 对于已知两数m,n,使得m>n;
    (2) m除以n得余数r;
    (3) 若r=0,则n为求得的最大公约数,算法结束;否则执行(4);
    (4) m←n,n←r,再重复执行(2)。
    例如: 求 m="14" ,n=6 的最大公约数. m n r
    14 6 2
    6 2 0
    void main()
    { int nm,r,n,m,t;
    printf("please input two numbers:\n");
    scanf("%d,%d",&m,&n);
    nm=n*m;
    if (m<n)
    { t="n"; n="m"; m="t"; }
    r=m%n;
    while (r!=0)
    { m="n"; n="r"; r="m"%n; }
    printf("最大公约数:%d\n",n);
    printf("最小公倍数:%d\n",nm/n);
    }

    三、判断素数
      只能被1或本身整除的数称为素数 基本思想:把m作为被除数,将2—INT( )作为除数,如果都除不尽,m就是素数,否则就不是。(可用以下程序段实现)
    void main()
    { int m,i,k;
    printf("please input a number:\n");
    scanf("%d",&m);
    k=sqrt(m);
    for(i=2;i<k;i++)
    if(m%i==0) break;
    if(i>=k)
    printf("该数是素数");
    else
    printf("该数不是素数");
    }
    将其写成一函数,若为素数返回1,不是则返回0
    int prime( m%)
    {int i,k;
    k=sqrt(m);
    for(i=2;i<k;i++)
    if(m%i==0) return 0;
    return 1;
    }

    四、验证哥德巴赫猜想
      (任意一个大于等于6的偶数都可以分解为两个素数之和)
    基本思想:n为大于等于6的任一偶数,可分解为n1和n2两个数,分别检查n1和n2是否为素数,如都是,则为一组解。如n1不是素数,就不必再检查n2是否素数。先从n1=3开始,检验n1和n2(n2=N-n1)是否素数。然后使n1+2 再检验n1、n2是否素数,… 直到n1=n/2为止。
      利用上面的prime函数,验证哥德巴赫猜想的程序代码如下:
    #include "math.h"
    int prime(int m)
    { int i,k;
    k=sqrt(m);
    for(i=2;i<k;i++)
    if(m%i==0) break;
    if(i>=k)
    return 1;
    else
    return 0;
    }
    main()
    { int x,i;
    printf("please input a even number(>=6):\n");
    scanf("%d",&x);
    if (x<6||x%2!=0)
    printf("data error!\n");
    else
    for(i=2;i<=x/2;i++)
    if (prime(i)&&prime(x-i))
    {
    printf("%d+%d\n",i,x-i);
    printf("验证成功!");
    break;
    }
    }

    五、排序问题
     1.选择法排序(升序)
      基本思想:
    1)对有n个数的序列(存放在数组a(n)中),从中选出最小的数,与第1个数交换位置;
    2)除第1 个数外,其余n-1个数中选最小的数,与第2个数交换位置;
    3)依次类推,选择了n-1次后,这个数列已按升序排列。
    程序代码如下:
    void main()
    { int i,j,imin,s,a[10];
    printf("\n input 10 numbers:\n");
    for(i=0;i<10;i++)
    scanf("%d",&a);
    for(i=0;i<9;i++)
    { imin="i";
    for(j=i+1;j<10;j++)
    if(a[imin]>a[j]) imin="j";
    if(i!=imin)
    {s=a; a=a[imin]; a[imin]=s; }
    printf("%d\n",a);
    }
    }
      2.冒泡法排序(升序)
      基本思想:(将相邻两个数比较,小的调到前头)
    1)有n个数(存放在数组a(n)中),第一趟将每相邻两个数比较,小的调到前头,经n-1次两两相邻比较后,最大的数已“沉底”,放在最后一个位置,小数上升“浮起”;
    2)第二趟对余下的n-1个数(最大的数已“沉底”)按上法比较,经n-2次两两相邻比较后得次大的数;
    3)依次类推,n个数共进行n-1趟比较,在第j趟中要进行n-j次两两比较。
    程序段如下
    void main()
    { int a[10];
    int i,j,t;
    printf("input 10 numbers\n");
    for(i=0;i<10;i++)
    scanf("%d",&a);
    printf("\n");
    for(j=0;j<=8;j++)
    for(i=0;i<9-j;i++)
    if(a>a[i+1])
    {t=a;a=a[i+1];a[i+1]=t;}
    printf("the sorted numbers:\n");
    for(i=0;i<10;i++)
    printf("%d\n",a);
    }
      3.合并法排序(将两个有序数组A、B合并成另一个有序的数组C,升序)
      基本思想:
    1)先在A、B数组中各取第一个元素进行比较,将小的元素放入C数组;
    2)取小的元素所在数组的下一个元素与另一数组中上次比较后较大的元素比较,重复上述比较过程,直到某个数组被先排完;
    3)将另一个数组剩余元素抄入C数组,合并排序完成。
    程序段如下:
    void main()
    { int a[10],b[10],c[20],i,ia,ib,ic;
    printf("please input the first array:\n");
    for(i=0;i<10;i++)
    scanf("%d",&a);
    for(i=0;i<10;i++)
    scanf("%d",&b);
    printf("\n");
    ia=0;ib=0;ic=0;
    while(ia<10&&ib<10)
    { if(a[ia]<b[ib])
    { c[ic]=a[ia];ia++;}
    else
    { c[ic]=b[ib];ib++;}
    ic++;
    }
    while(ia<=9)
    { c[ic]=a[ia];
    ia++;ic++;
    }
    while(ib<=9)
    { c[ic]=b[ib];
    b++;ic++;
    }
    for(i=0;i<20;i++)
    printf("%d\n",c);
    }

    六、查找问题
      顺序查找法(在一列数中查找某数x)
    基本思想:一列数放在数组a[1]---a[n]中,待查找的数放在x 中,把x与a数组中的元素从头到尾一一进行比较查找。用变量p表示a数组元素下标,p初值为1,使x与a[p]比较,如果x不等于a[p],则使p=p+1,不断重复这个过程;一旦x等于a[p]则退出循环;另外,如果p大于数组长度,循环也应该停止。(这个过程可由下语句实现)
    void main()
    { int a[10],p,x,i;
    printf("please input the array:\n");
    for(i=0;i<10;i++)
    scanf("%d",&a);
    printf("please input the number you want find:\n");
    scanf("%d",&x);
    printf("\n");
    p=0;
    while(x!=a[p]&&p<10)
    p++;
    if(p>=10)
    printf("the number is not found!\n");
    else
    printf("the number is found the no%d!\n",p);
    }
    思考:将上面程序改写一查找函数Find,若找到则返回下标值,找不到返回-1
    ②基本思想:一列数放在数组a[1]---a[n]中,待查找的关键值为key,把key与a数组中的元素从头到尾一一进行比较查找,若相同,查找成功,若找不到,则查找失败。(查找子过程如下。index:存放找到元素的下标。)
    void main()
    { int a[10],index,x,i;
    printf("please input the array:\n");
    for(i=0;i<10;i++)
    scanf("%d",&a);
    printf("please input the number you want find:\n");
    scanf("%d",&x);
    printf("\n");
    index=-1;
    for(i=0;i<10;i++)
    if(x==a)
    { index="i"; break;
    }
    if(index==-1)
    printf("the number is not found!\n");
    else
    printf("the number is found the no%d!\n",index);
    }

    七、二分法
    在一个数组中,知道一个数值,想确定他在数组中的位置下标,如数组:A[5] = {1,2,6,7,9};我知道其中的值为6,那么他的下标位置就是3。
    int Dichotomy(int *ucData, int long, int num)
    {
       int iDataLow  = 0 ;
       int iDataHigh = num - 1;
       int iDataMIDDLE;
       while (iDataLow <= iDataHigh)
      {
         iDataMIDDLE = (iDataHigh + iDataLow)/2;
         i f (ucData[iDataMIDDLE] > long)
         {
           iDataHigh = iDataMIDDLE - 1 ;
         }    
         else if (ucData[iDataMIDDLE] < long)
      {
       iDataLow = iDataMIDDLE + 1 ;
      }  else{
       return iDataMIDDLE ;
      }
    }
    }

    八、限幅滤波法
    对于随机干扰 , 限幅滤波是一种有效的方法;
    基本方法:比较相邻n 和 n - 1时刻的两个采样值y(n)和 y(n – 1),根据经验确定两次采样允许的最大偏差。如果两次采样值的差值超过最大偏差范围 ,认为发生可随机干扰 ,并认为后一次采样值y(n)为非法值 ,应予删除 ,删除y(n)后 ,可用y(n – 1) 代替y(n);若未超过所允许的最大偏差范围 ,则认为本次采样值有效。
    下面是限幅滤波程序:( A 值可根据实际情况调整,value 为有效值 ,new_value 为当前采样值滤波程序返回有效的实际值 )
    #define A 10
    char value;
    char filter()
    {   char new_value;
        new_value = get_ad();
        if ( ( new_value - value > A ) || ( value - new_value > A ))  return value;
        return new_value;
    }

    九、中位值滤波法
    中位值滤波法能有效克服偶然因素引起的波动或采样不稳定引起的误码等脉冲干扰;
    对温度 液位等缓慢变化的被测参数用此法能收到良好的滤波效果 ,但是对于流量压力等快速变化的参数一般不宜采用中位值滤波法;
    基本方法:对某一被测参数连续采样 n次(一般 n 取奇数) ,然后再把采样值按大小排列 ,取中间值为本次采样值。
    下面是中位值滤波程序:
    #define N   11
    char filter()
    {  char value_buf[N], count,i,j,temp;
        for ( count=0;count<N;count++)
        {  value_buf[count] = get_ad();    delay();   }
        for (j=0;j<N-1;j++)
        {  for (i=0;i<N-j;i++)
             {  if ( value_buf>value_buf[i+1] )
                 {temp = value_buf; value_buf = value_buf[i+1]; value_buf[i+1] = temp;  }
             }
        }
        return value_buf[(N-1)/2];
    }


    十.算术平均滤波法
    算术平均滤波法适用于对一般的具有随机干扰的信号进行滤波。这种信号的特点是信号本身在某一数值范围附近上下波动 ,如测量流量、 液位;
    基本方法:按输入的N 个采样数据 ,寻找这样一个 Y ,使得 Y 与各个采样值之间的偏差的平方和最小。
    编写算术平均滤波法程序时严格注意:
           一.为了加快数据测量的速度 ,可采用先测量数据 存放在存储器中 ,测完 N 点后 ,再对 N 个数据进行平均值计算;
           二.选取适当的数据格式 ,也就是说采用定点数还是采用浮点数。其程序如下所示:
    #define N 12
    char filter()
    {int   sum = 0,count;
          for ( count=0;count<N;count++)
          {  sum+=get_ad();    delay();}
    return (char)(sum/N);
    }

    十一、递推平均滤波法
    基本方法:采用队列作为测量数据存储器 ,   设队列的长度为 N ,每进行一次测量 ,把测量结果放于队尾 ,而扔掉原来队首的一个数据 ,这样在队列中始终就有 N 个 “最新” 的数据。当计算平均值时 ,只要把队列中的 N 个数据进行算数平均 ,就可得到新的算数平均值。这样每进行一次测量 ,就可得到一个新的算术平均值。
    #define N 12
    char value_buf[N],i=0;
    char filter()
    { char count; int   sum=0;
      value_buf[i++] = get_ad();
    if ( i == N )    i = 0;
    for ( count=0;count<N;count++)
         sum = value_buf[count];
    return (char)(sum/N);
    }

    十二、一阶滞后滤波法
    优点:对周期性干扰具有良好的抑制作用,适用于波动频率较高的场合;
    缺点:相位滞后,灵敏度低.滞后程度取决于a值大小.不能消除滤波频率高于采样频率的1/2的干扰信号。程序如下:
    #define a 50
    char value;
    char filter()
    { char   new_value;
       new_value = get_ad();
       return (100-a)*value + a*new_value;
    }

    十三、PID控制算法
    在过程控制中,按偏差的比例(P)、积分(I)和微分(D)进行控制的PID控制器(亦称PID调节器)是应用最为广泛的一种自动控制器;
    对于过程控制的典型对象──“一阶滞后+纯滞后”与“二阶滞后+纯滞后”的控制对象,PID控制器是一种最优控制;
    PID调节规律是连续系统动态品质校正的一种有效方法,它的参数整定方式简便,结构改变灵活(PI、PD、…)。
    一  模拟PID调节器




    模拟PID控制系统原理框图


    PID调节器各校正环节的作用:
    比例环节:即时成比例地反应控制系统的偏差信号e(t),偏差一旦产生,调节器立即产生控制作用以减小偏差;
    积分环节:主要用于消除静差,提高系统的无差度。积分时间常数TI越大,积分作用越弱,反之则越强;
    微分环节:能反应偏差信号的变化趋势(变化速率),并能在偏差信号的值变得太大之前,在系统中引入一个有效的早期修正信号,从而加快系统的动作速度,减小调节时间。
           PID调节器是一种线性调节器,它将给定值r(t)与实际输出值c(t)的偏差的比例(P)、积分(I)、微分(D)通过线性组合构成控制量,对控制对象进行控制。











    程序片段如下:
    #include <reg52.h>
    #include <string.h>            
    typedef struct PID {
    double SetPoint;     // 设定目标Desired value
    double Proportion;    // 比例常数Proportional Const
    double Integral;      // 积分常数Integral Const
    double Derivative;    // 微分常数Derivative Const
    double LastError;    // Error[-1]

    double PrevError;    // Error[-2]
    double SumError;   // Sums of Errors
    } PID;

    主程序:
    double sensor (void)
    {
    return 100.0; }
    void actuator(double rDelta)
    {}
    void main(void)
    {
    PID sPID;
    double rOut;
    double rIn;
    PIDInit ( &sPID );
    sPID.Proportion = 0.5;
    sPID.Derivative = 0.0;
    sPID.SetPoint = 100.0;

    for (;;) {
    rIn = sensor ();
    rOut = PIDCalc ( &sPID,rIn );
    actuator ( rOut );
    }
    }

    十四、开根号算法
    单片机开平方的快速算法
      因为工作的需要,要在单片机上实现开根号的操作。目前开平方的方法大部分是用牛顿迭代法。我在查了一些资料以后找到了一个比牛顿迭代法更加快速的方法。不敢独享,介绍给大家,希望会有些帮助。

    1.原理
    因为排版的原因,用pow(X,Y)表示X的Y次幂,用B[0],B[1],...,B[m-1]表示一个序列,其中[x]为下标。

    假设:
       B[x],b[x]都是二进制序列,取值0或1。
       M = B[m-1]*pow(2,m-1) + B[m-2]*pow(2,m-2) + ... + B[1]*pow(2,1) + B[0]*pow(2,0)
       N = b[n-1]*pow(2,n-1) + b[n-2]*pow(2,n-2) + ... + b[1]*pow(2,1) + n[0]*pow(2,0)
       pow(N,2) = M

       (1) N的最高位b[n-1]可以根据M的最高位B[m-1]直接求得。
       设 m 已知,因为 pow(2, m-1) <= M <= pow(2, m),所以 pow(2, (m-1)/2) <= N <= pow(2, m/2)
       如果 m 是奇数,设m=2*k+1,
       那么 pow(2,k) <= N < pow(2, 1/2+k) < pow(2, k+1),
       n-1=k, n=k+1=(m+1)/2
       如果 m 是偶数,设m=2k,
       那么 pow(2,k) > N >= pow(2, k-1/2) > pow(2, k-1),
       n-1=k-1,n=k=m/2
       所以b[n-1]完全由B[m-1]决定。
       余数 M[1] = M - b[n-1]*pow(2, 2*n-2)

       (2) N的次高位b[n-2]可以采用试探法来确定。
       因为b[n-1]=1,假设b[n-2]=1,则 pow(b[n-1]*pow(2,n-1) + b[n-1]*pow(2,n-2), 2) = b[n-1]*pow(2,2*n-2) + (b[n-1]*pow(2,2*n-2) + b[n-2]*pow(2,2*n-4)),
       然后比较余数M[1]是否大于等于 (pow(2,2)*b[n-1] + b[n-2]) * pow(2,2*n-4)。这种比较只须根据B[m-1]、B[m-2]、...、B[2*n-4]便可做出判断,其余低位不做比较。
       若 M[1] >= (pow(2,2)*b[n-1] + b[n-2]) * pow(2,2*n-4), 则假设有效,b[n-2] = 1;
       余数 M[2] = M[1] - pow(pow(2,n-1)*b[n-1] + pow(2,n-2)*b[n-2], 2) = M[1] - (pow(2,2)+1)*pow(2,2*n-4);
       若 M[1] < (pow(2,2)*b[n-1] + b[n-2]) * pow(2,2*n-4), 则假设无效,b[n-2] = 0;余数 M[2] = M[1]。

       (3) 同理,可以从高位到低位逐位求出M的平方根N的各位。

    使用这种算法计算32位数的平方根时最多只须比较16次,而且每次比较时不必把M的各位逐一比较,尤其是开始时比较的位数很少,所以消耗的时间远低于牛顿迭代法。

    3. 实现代码
    这里给出实现32位无符号整数开方得到16位无符号整数的C语言代码。

    /****************************************/
    /*Function: 开根号处理                  */
    /*入口参数:被开方数,长整型            */
    /*出口参数:开方结果,整型              */
    /****************************************/
    unsigned int sqrt_16(unsigned long M)
    {
         unsigned int N, i;
        unsigned long tmp, ttp;   // 结果、循环计数
        if (M == 0)               // 被开方数,开方结果也为0
            return 0;

        N = 0;

        tmp = (M >> 30);          // 获取最高位:B[m-1]
        M <<= 2;
        if (tmp > 1)              // 最高位为1
        {
            N ++;                 // 结果当前位为1,否则为默认的0
            tmp -= N;
        }

        for (i=15; i>0; i--)      // 求剩余的15位
        {
            N <<= 1;              // 左移一位

            tmp <<= 2;
            tmp += (M >> 30);     // 假设

            ttp = N;
            ttp = (ttp<<1)+1;

            M <<= 2;
            if (tmp >= ttp)       // 假设成立
            {
                tmp -= ttp;
                N ++;
            }

        }

        return N;
    }


    展开全文
  • 连接:http://bbs.21ic.com/icview-170880-1-1.html -------------------------------------------------以下为原文 ------------------- 连接:...单片机大多资源小,算法占用

    连接:http://bbs.21ic.com/icview-170880-1-1.html

    -------------------------------------------------以下为原文 -------------------

    连接:http://bbs.21ic.com/icview-170880-1-1.html

     

    单片机大多资源小,算法占用的资源越小越好,现在介绍就是一个占用很小资源的算法,这个算法是本人在进行扫描仪设计,实现灰度转二值时实现动态阈值,当时为了跟踪灰度等级的变化,需要一个灰度积分跟踪电路,开始使用一个电容积分电路,用灰度信号对电容充电,放电时以该电容电压的比例进行,实现对输入信号的跟踪,但用电容的电路设计比较复杂。过后发现这种比例放电的思想用软件实现非常简单,且具有积分、微分的作用。
    具体公式如下:
           SUM=SUM-SUM/n+S 
           其中:S为采样值,SUM为保存值,n是放电比例、最好选2的幂次数,单片机移位即可,不需要做除法,跟随后得到的值为SUM/nSUM注意不溢出,预留的容量为采样数最大值的n倍,初始化时如果是跟踪一段时间后使用,可以是任何值,否则可以用采样值乘n初始化。使用值为SUM/n(下文中SA),实现SUM/nS的跟踪。还有一个关键是计算周期T,即多长时间进行一次。
    一、积分作用:
    1.平滑滤波(滑动平均滤波)
    由公式中可以看出,每次采样、计算后,当前采样的影响对SUM/n只有1/n,而且采到的值随次数的增加影响越来越小直至没有,相关性逐渐减弱,而且是连续相关。如果计算周期与采样周期相同,使用计算后的值对干扰有n倍的抑制,即积分的平滑滤波作用,如1ms采样一次,同时运算一次,
    则使用值SA=SUM/n为抑制干扰的结果,且同样是1ms给出一个结果,使用两个变量实现平滑滤波,并且是即时使用的,与采样几次平均的平滑不同。



     

     

    2.动态阈值
    在很多应用中需要动态阈值,比如触摸按键的键阈值门限,血压计的心率检出,前面提到的灰度转二值黑白图像等(灰度转二值因为扫描速度2.5Mbyte/S,不能使用软件运算,但可以使用可编程逻辑实现)。动态阈值是对信号积分后得到的低频变化再与基本门限相加在触摸按键中增加动态阈值可以提高其适应性和可靠性。关键是根据按键反应时间和按键间隔确定按键积分参数,跟踪速度,n、T越大跟踪的越慢,积分效果越好。
     

    3.锁相作用:把上边的积分运算,用于对时间上周期的信号,例如根据过零触发信号锁定交流电源周期,使用两次T时间不同,其它相同的运算,由于T不同,跟踪速度不同,当两次运算的结果相等时可以确认为锁定,这时得到的是准确的电源周期值,而相位偏差也很小。

    二、微分作用:
    公式中的SA趋近采样值S,如果S是线性的,SA的值是可控滞后于S,那么运算的间隔时间T不同,得到的跟踪曲线的滞后特性不同,这种滞后特性的差和间隔时间就是微分特性,表示曲线的变化规律。如电热水壶,温度的变化相当于采样时间是还相当慢的,局部可以作为线性变化来处理。下边以设计电热水壶的过程来说明微分作用。
    电热水壶出口一直使用蒸汽开关这种需要交专利费的方式。不使用蒸汽开关检测压力只能使用热敏器件检测温度。
    温度检测的环境要求:
    1.        海拔高度不同的地区水开的温度不同。
    2.        热敏器件的误差较大,必须克服,否则可生产性不足。
    3.        环境温度不同,电源电压不同,装水量不同。
    由要求1、2决定检测温度不能判别水开与否,需要检测温度的变化率,但温度变化率的判别又和要求3相关,下边曲线图为热水器的加热曲线。蓝线为即时温度,橙色为一次运算后的曲线。

    图中加热过程中间添加了冷水,曲线有一段下降,过后的加热过程两个曲线有个差异滞后,同一个时间的两个曲线差表示了加热效率的变化,其中最大的加热效率体现了环境温度不同,电源电压不同,装水量不同的综合效果。由于滞后的时间可以通过计算周期T来调整,知道滞后时间又有相减的差,这就是微分效应,加热过程整个就是效率的变化过程。我们可以通过1秒钟计算一次,2秒钟计算一次,加上原始数据得到三个曲线,效率的变化一目了然。
            第一次的水开检测使用效率的方法,同时也会得到水开时的温度检测值,微分特性本身是可以预知变化趋势的,如果1秒钟计算一次,用当前检测值减去这次计算的结果,这个差与当前值相加,就可以做为当前1秒后的结果,也就是预知1秒后加热的检测值,结合第一次得到的水开温度检测值,以后的水开检测就有两个判断条件。

     

    ---------------------------------------------- 以下为匠人的分析-----------------------------

    拨开迷雾看真相,作者的这个算法,本质上,就是一阶滤波(低通滤波)。

    引用作者原来的公式

    SUM=SUM-SUM/n+S
    首先点破一下,等号前面的SUM代表的是本次运算结果,而等号后面的SUM代表的是上次运算结果。

    且看匠人如何推导: 
    设:
    SUM=A
    SUM/n=B=本次滤波结果
    1/n=a (一阶滤波系数)
    S=本次新采样值

    则:
    A=nB
    B=A/n


    另外:
    A代表本次值
    A’B’ 代表上次值

    作者原公式逐步推导:
    原始:SUM=SUM-SUM/n+S
    1步:A=A’ – A’/n +S
    2步:nB=nB’ – B’ +S
    3步:B= (nB’ – B’ +S)/n
    4步:B=B’ –B’/n +S/n
    5步:B=1- 1/nB’ + 1/n*S
    6步:B= 1-aB’+ a *S

    推导到最后一步,是不是很眼熟啦?
    呵呵,这就是经典的一阶滤波(低通滤波)的标准公式了

    展开全文
  • 算法(Algorithm):计算机解题的基本思想方法和步骤。 算法的描述:是对要解决一个问题或要完成一项任务所采取的方法和步骤的描述,包括需要什么数据(输入什么数据、输出什么结果)、采用什么结构、使用什么语句...

    算法(Algorithm):计算机解题的基本思想方法和步骤。

    算法的描述:是对要解决一个问题或要完成一项任务所采取的方法和步骤的描述,包括需要什么数据(输入什么数据、输出什么结果)、采用什么结构、使用什么语句以及如何安排这些语句等。通常使用自然语言、结构化流程图、伪代码等来描述算法。

    一、计数、求和、求阶乘等简单算法
    此类问题都要使用循环,要注意根据问题确定循环变量的初值、终值或结束条件,更要注意用来表示计数、和、阶乘的变量的初值。

    例:用随机函数产生100个[0,99]范围内的随机整数,统计个位上的数字分别为1,2,3,4,5,6,7,8,9,0的数的个数并打印出来。

    本题使用数组来处理,用数组a[100]存放产生的确100个随机整数,数组x[10]来存放个位上的数字分别为1,2,3,4,5,6,7,8,9,0的数的个数。即个位是1的个数存放在x[1]中,个位是2的个数存放在x[2]中,……个位是0的个数存放在x[10]。
    void main()
    {
    int a[101],x[11],i,p;
    for(i=0;i<=11;i++)
    x=0;
    for(i=1;i<=100;i++)
    {
    a=rand() % 100;
    printf("%4d",a);
    if(i%100)printf("\n");
    }
    for(i=1;i<=100;i++)
    {
    p=“a”%10;
    if(p
    0) p=“10”;
    x[p]=x[p]+1;
    }
    for(i=1;i<=10;i++)
    {
    p=“i”;
    if(i==10) p=“0”;
    printf("%d,%d\n",p,x);
    }
    printf("\n");
    }

    二、求两个整数的最大公约数、最小公倍数
    分析:求最大公约数的算法思想:(最小公倍数=两个整数之积/最大公约数)
    (1) 对于已知两数m,n,使得m>n;
    (2) m除以n得余数r;
    (3) 若r=0,则n为求得的最大公约数,算法结束;否则执行(4);
    (4) m←n,n←r,再重复执行(2)。
    例如: 求 m=“14” ,n=6 的最大公约数. m n r
    14 6 2
    6 2 0
    void main()
    { int nm,r,n,m,t;
    printf(“please input two numbers:\n”);
    scanf("%d,%d",&m,&n);
    nm=n*m;
    if (m
    { t=“n”; n=“m”; m=“t”; }
    r=m%n;
    while (r!=0)
    { m=“n”; n=“r”; r=“m”%n; }
    printf(“最大公约数:%d\n”,n);
    printf(“最小公倍数:%d\n”,nm/n);
    }

    三、判断素数
    只能被1或本身整除的数称为素数 基本思想:把m作为被除数,将2—INT( )作为除数,如果都除不尽,m就是素数,否则就不是。(可用以下程序段实现)
    void main()
    { int m,i,k;
    printf(“please input a number:\n”);
    scanf("%d",&m);
    k=sqrt(m);
    for(i=2;i
    if(m%i0) break;
    if(i>=k)
    printf(“该数是素数”);
    else
    printf(“该数不是素数”);
    }
    将其写成一函数,若为素数返回1,不是则返回0
    int prime( m%)
    {int i,k;
    k=sqrt(m);
    for(i=2;i
    if(m%i
    0) return 0;
    return 1;
    }

    四、验证哥德巴赫猜想
    (任意一个大于等于6的偶数都可以分解为两个素数之和)
    基本思想:n为大于等于6的任一偶数,可分解为n1和n2两个数,分别检查n1和n2是否为素数,如都是,则为一组解。如n1不是素数,就不必再检查n2是否素数。先从n1=3开始,检验n1和n2(n2=N-n1)是否素数。然后使n1+2 再检验n1、n2是否素数,… 直到n1=n/2为止。
    利用上面的prime函数,验证哥德巴赫猜想的程序代码如下:
    #include “math.h”
    int prime(int m)
    { int i,k;
    k=sqrt(m);
    for(i=2;i
    if(m%i==0) break;
    if(i>=k)
    return 1;
    else
    return 0;
    }
    main()
    { int x,i;
    printf(“please input a even number(>=6):\n”);
    scanf("%d",&x);
    if (x<6||x%2!=0)
    printf(“data error!\n”);
    else
    for(i=2;i<=x/2;i++)
    if (prime(i)&&prime(x-i))
    {
    printf("%d+%d\n",i,x-i);
    printf(“验证成功!”);
    break;
    }
    }
    五、排序问题
    1.选择法排序(升序)
    基本思想:
    1)对有n个数的序列(存放在数组a(n)中),从中选出最小的数,与第1个数交换位置;
    2)除第1 个数外,其余n-1个数中选最小的数,与第2个数交换位置;
    3)依次类推,选择了n-1次后,这个数列已按升序排列。
    程序代码如下:
    void main()
    { int i,j,imin,s,a[10];
    printf("\n input 10 numbers:\n");
    for(i=0;i<10;i++)
    scanf("%d",&a);
    for(i=0;i<9;i++)
    { imin=“i”;
    for(j=i+1;j<10;j++)
    if(a[imin]>a[j]) imin=“j”;
    if(i!=imin)
    {s=a; a=a[imin]; a[imin]=s; }
    printf("%d\n",a);
    }
    }

    2.冒泡法排序(升序)
    基本思想:(将相邻两个数比较,小的调到前头)
    1)有n个数(存放在数组a(n)中),广州小学四年级数学视频辅导第一趟将每相邻两个数比较,小的调到前头,经n-1次两两相邻比较后,最大的数已“沉底”,放在最后一个位置,小数上升“浮起”;
    2)第二趟对余下的n-1个数(最大的数已“沉底”)按上法比较,经n-2次两两相邻比较后得次大的数;
    3)依次类推,n个数共进行n-1趟比较,在第j趟中要进行n-j次两两比较。
    程序段如下
    void main()
    { int a[10];
    int i,j,t;
    printf(“input 10 numbers\n”);
    for(i=0;i<10;i++)
    scanf("%d",&a);
    printf("\n");
    for(j=0;j<=8;j++)
    for(i=0;i<9-j;i++)
    if(a>a[i+1])
    {t=a;a=a[i+1];a[i+1]=t;}
    printf(“the sorted numbers:\n”);
    for(i=0;i<10;i++)
    printf("%d\n",a);
    }

    3.合并法排序(将两个有序数组A、B合并成另一个有序的数组C,升序)
    基本思想:
    1)先在A、B数组中各取第一个元素进行比较,将小的元素放入C数组;
    2)取小的元素所在数组的下一个元素与另一数组中上次比较后较大的元素比较,重复上述比较过程,直到某个数组被先排完;
    3)将另一个数组剩余元素抄入C数组,合并排序完成。

    程序段如下:
    void main()
    { int a[10],b[10],c[20],i,ia,ib,ic;
    printf(“please input the first array:\n”);
    for(i=0;i<10;i++)
    scanf("%d",&a);
    for(i=0;i<10;i++)
    scanf("%d",&b);
    printf("\n");
    ia=0;ib=0;ic=0;
    while(ia<10&&ib<10)
    { if(a[ia]
    { c[ic]=a[ia];ia++;}
    else
    { c[ic]=b[ib];ib++;}
    ic++;
    }
    while(ia<=9)
    { c[ic]=a[ia];
    ia++;ic++;
    }
    while(ib<=9)
    { c[ic]=b[ib];
    b++;ic++;
    }
    for(i=0;i<20;i++)
    printf("%d\n",c);
    }

    六、查找问题
    顺序查找法(在一列数中查找某数x)
    基本思想:一列数放在数组a[1]—a[n]中,待查找的数放在x 中,把x与a数组中的元素从头到尾一一进行比较查找。用变量p表示a数组元素下标,p初值为1,使x与a[p]比较,如果x不等于a[p],则使p=p+1,不断重复这个过程;一旦x等于a[p]则退出循环;另外,如果p大于数组长度,循环也应该停止。(这个过程可由下语句实现)
    void main()
    { int a[10],p,x,i;
    printf(“please input the array:\n”);
    for(i=0;i<10;i++)
    scanf("%d",&a);
    printf(“please input the number you want find:\n”);
    scanf("%d",&x);
    printf("\n");
    p=0;
    while(x!=a[p]&&p<10)
    p++;
    if(p>=10)
    printf(“the number is not found!\n”);
    else
    printf(“the number is found the no%d!\n”,p);
    }

    思考:将上面程序改写一查找函数Find,若找到则返回下标值,找不到返回-1

    ②基本思想:一列数放在数组a[1]—a[n]中,待查找的关键值为key,把key与a数组中的元素从头到尾一一进行比较查找,若相同,查找成功,若找不到,则查找失败。(查找子过程如下。index:存放找到元素的下标。)

    void main()
    { int a[10],index,x,i;
    printf(“please input the array:\n”);
    for(i=0;i<10;i++)
    scanf("%d",&a);
    printf(“please input the number you want find:\n”);
    scanf("%d",&x);
    printf("\n");
    index=-1;
    for(i=0;i<10;i++)
    if(xa)
    { index=“i”; break;
    }
    if(index
    -1)
    printf(“the number is not found!\n”);
    else
    printf(“the number is found the no%d!\n”,index);
    }
    七、二分法
    在一个数组中,知道一个数值,想确定他在数组中的位置下标,如数组:A[5] = {1,2,6,7,9};我知道其中的值为6,那么他的下标位置就是3。
    int Dichotomy(int *ucData, int long, int num)
    {
    int iDataLow = 0 ;
    int iDataHigh = num - 1;
    int iDataMIDDLE;
    while (iDataLow <= iDataHigh)
    {
    iDataMIDDLE = (iDataHigh + iDataLow)/2;
    i f (ucData[iDataMIDDLE] > long)
    {
    iDataHigh = iDataMIDDLE - 1 ;
    }
    else if (ucData[iDataMIDDLE] < long)
    {
    iDataLow = iDataMIDDLE + 1 ;
    } else{
    return iDataMIDDLE ;
    }
    }
    }
    八、限幅滤波法
    对于随机干扰 , 限幅滤波是一种有效的方法;
    基本方法:比较相邻n 和 n - 1时刻的两个采样值y(n)和 y(n – 1),根据经验确定两次采样允许的最大偏差。如果两次采样值的差值超过最大偏差范围 ,认为发生可随机干扰 ,并认为后一次采样值y(n)为非法值 ,应予删除 ,删除y(n)后 ,可用y(n – 1) 代替y(n);若未超过所允许的最大偏差范围 ,则认为本次采样值有效。

    下面是限幅滤波程序:( A 值可根据实际情况调整,value 为有效值 ,new_value 为当前采样值滤波程序返回有效的实际值 )
    #define A 10
    char value;
    char filter()
    { char new_value;
    new_value = get_ad();
    if ( ( new_value - value > A ) || ( value - new_value > A )) return value;
    return new_value;
    }
    九、中位值滤波法
    中位值滤波法能有效克服偶然因素引起的波动或采样不稳定引起的误码等脉冲干扰;
    对温度 液位等缓慢变化的被测参数用此法能收到良好的滤波效果 ,但是对于流量压力等快速变化的参数一般不宜采用中位值滤波法;
    基本方法:对某一被测参数连续采样 n次(一般 n 取奇数) ,然后再把采样值按大小排列 ,取中间值为本次采样值。
    下面是中位值滤波程序:
    #define N 11
    char filter()
    { char value_buf[N], count,i,j,temp;
    for ( count=0;count
    { value_buf[count] = get_ad(); delay(); }
    for (j=0;j
    { for (i=0;i
    { if ( value_buf>value_buf[i+1] )
    {temp = value_buf; value_buf = value_buf[i+1]; value_buf[i+1] = temp; }
    }
    }
    return value_buf[(N-1)/2];
    }

    十.算术平均滤波法
    算术平均滤波法适用于对一般的具有随机干扰的信号进行滤波。这种信号的特点是信号本身在某一数值范围附近上下波动 ,如测量流量、 液位;
    基本方法:按输入的N 个采样数据 ,寻找这样一个 Y ,使得 Y 与各个采样值之间的偏差的平方和最小。

    编写算术平均滤波法程序时严格注意:
    一.为了加快数据测量的速度 ,可采用先测量数据 存放在存储器中 ,测完 N 点后 ,再对 N 个数据进行平均值计算;
    二.选取适当的数据格式 ,也就是说采用定点数还是采用浮点数。其程序如下所示:
    #define N 12
    char filter()
    {int sum = 0,count;
    for ( count=0;count
    { sum+=get_ad(); delay();}
    return (char)(sum/N);
    }

    十一、递推平均滤波法
    基本方法:采用队列作为测量数据存储器 , 设队列的长度为 N ,每进行一次测量 ,把测量结果放于队尾 ,而扔掉原来队首的一个数据 ,这样在队列中始终就有 N 个 “最新” 的数据。当计算平均值时 ,只要把队列中的 N 个数据进行算数平均 ,就可得到新的算数平均值。这样每进行一次测量 ,就可得到一个新的算术平均值。
    #define N 12
    char value_buf[N],i=0;
    char filter()
    { char count; int sum=0;
    value_buf[i++] = get_ad();
    if ( i == N ) i = 0;
    for ( count=0;count
    sum = value_buf[count];
    return (char)(sum/N);
    }

    十二、一阶滞后滤波法
    优点:对周期性干扰具有良好的抑制作用,适用于波动频率较高的场合;
    缺点:相位滞后,灵敏度低.滞后程度取决于a值大小.不能消除滤波频率高于采样频率的1/2的干扰信号。程序如下:
    #define a 50
    char value;
    char filter()
    { char new_value;
    new_value = get_ad();
    return (100-a)value + anew_value;
    }

    十三、PID控制算法
    在过程控制中,按偏差的比例(P)、积分(I)和微分(D)进行控制的PID控制器(亦称PID调节器)是应用最为广泛的一种自动控制器;
    对于过程控制的典型对象──“一阶滞后+纯滞后”与“二阶滞后+纯滞后”的控制对象,PID控制器是一种最优控制;
    PID调节规律是连续系统动态品质校正的一种有效方法,它的参数整定方式简便,结构改变灵活(PI、PD、…)。

    一 模拟PID调节器
    PID调节器各校正环节的作用:
    比例环节:即时成比例地反应控制系统的偏差信号e(t),偏差一旦产生,调节器立即产生控制作用以减小偏差;
    积分环节:主要用于消除静差,提高系统的无差度。积分时间常数TI越大,积分作用越弱,反之则越强;
    微分环节:能反应偏差信号的变化趋势(变化速率),并能在偏差信号的值变得太大之前,在系统中引入一个有效的早期修正信号,从而加快系统的动作速度,减小调节时间。
    PID调节器是一种线性调节器,它将给定值r(t)与实际输出值c(t)的偏差的比例§、积分(I)、微分(D)通过线性组合构成控制量,对控制对象进行控制。
    程序片段如下:
    #include
    #include
    typedef struct PID {
    double SetPoint; // 设定目标Desired value
    double Proportion; // 比例常数Proportional Const
    double Integral; // 积分常数Integral Const
    double Derivative; // 微分常数Derivative Const
    double LastError; // Error[-1]
    double PrevError; // Error[-2]
    double SumError; // Sums of Errors
    } PID;

    主程序:
    double sensor (void)
    {
    return 100.0; }
    void actuator(double rDelta)
    {}
    void main(void)
    {
    PID sPID;
    double rOut;
    double rIn;
    PIDInit ( &sPID );
    sPID.Proportion = 0.5;
    sPID.Derivative = 0.0;
    sPID.SetPoint = 100.0;
    for (;? {
    rIn = sensor ();
    rOut = PIDCalc ( &sPID,rIn );
    actuator ( rOut );
    }
    }

    十四、开根号算法
    单片机开平方的快速算法
    因为工作的需要,要在单片机上实现开根号的操作。目前开平方的方法大部分是用牛顿迭代法。我在查了一些资料以后找到了一个比牛顿迭代法更加快速的方法。不敢独享,介绍给大家,希望会有些帮助。
    1.原理
    因为排版的原因,用pow(X,Y)表示X的Y次幂,用B[0],B[1],…,B[m-1]表示一个序列,其中[x]为下标。
    假设:
    B[x],b[x]都是二进制序列,取值0或1。
    M = B[m-1]*pow(2,m-1) + B[m-2]*pow(2,m-2) + … + B[1]*pow(2,1) + B[0]*pow(2,0)
    N = b[n-1]*pow(2,n-1) + b[n-2]*pow(2,n-2) + … + b[1]*pow(2,1) + n[0]pow(2,0)
    pow(N,2) = M
    (1) N的最高位b[n-1]可以根据M的最高位B[m-1]直接求得。
    设 m 已知,因为 pow(2, m-1) <= M <= pow(2, m),所以 pow(2, (m-1)/2) <= N <= pow(2, m/2)
    如果 m 是奇数,设m=2
    k+1,
    那么 pow(2,k) <= N < pow(2, 1/2+k) < pow(2, k+1),
    n-1=k, n=k+1=(m+1)/2
    如果 m 是偶数,设m=2k,
    那么 pow(2,k) > N >= pow(2, k-1/2) > pow(2, k-1),
    n-1=k-1,n=k=m/2
    所以b[n-1]完全由B[m-1]决定。
    余数 M[1] = M - b[n-1]pow(2, 2n-2)
    (2) N的次高位b[n-2]可以采用试探法来确定。
    因为b[n-1]=1,假设b[n-2]=1,则 pow(b[n-1]*pow(2,n-1) + b[n-1]pow(2,n-2), 2) = b[n-1]pow(2,2n-2) + (b[n-1]pow(2,2n-2) + b[n-2]pow(2,2n-4)),
    然后比较余数M[1]是否大于等于 (pow(2,2)b[n-1] + b[n-2]) * pow(2,2n-4)。这种比较只须根据B[m-1]、B[m-2]、…、B[2
    n-4]便可做出判断,其余低位不做比较。
    若 M[1] >= (pow(2,2)b[n-1] + b[n-2]) * pow(2,2n-4), 则假设有效,b[n-2] = 1;
    余数 M[2] = M[1] - pow(pow(2,n-1)*b[n-1] + pow(2,n-2)*b[n-2], 2) = M[1] - (pow(2,2)+1)pow(2,2n-4);
    若 M[1] < (pow(2,2)b[n-1] + b[n-2]) * pow(2,2n-4), 则假设无效,b[n-2] = 0;余数 M[2] = M[1]。
    (3) 同理,可以从高位到低位逐位求出M的平方根N的各位。
    使用这种算法计算32位数的平方根时最多只须比较16次,而且每次比较时不必把M的各位逐一比较,尤其是开始时比较的位数很少,所以消耗的时间远低于牛顿迭代法。

    1. 实现代码
      这里给出实现32位无符号整数开方得到16位无符号整数的C语言代码。
      /******/
      /Function: 开根号处理 /
      /入口参数:被开方数,长整型 /
      /出口参数:开方结果,整型 /
      /
      /
      unsigned int sqrt_16(unsigned long M)
      {
      unsigned int N, i;
      unsigned long tmp, ttp; // 结果、循环计数
      if (M == 0) // 被开方数,开方结果也为0
      return 0;
      N = 0;
      tmp = (M >> 30); // 获取最高位:B[m-1]
      M <<= 2;
      if (tmp > 1) // 最高位为1
      {
      N ++; // 结果当前位为1,否则为默认的0
      tmp -= N;
      }
      for (i=15; i>0; i–) // 求剩余的15位
      {
      N <<= 1; // 左移一位
      tmp <<= 2;
      tmp += (M >> 30); // 假设
      ttp = N;
      ttp = (ttp<<1)+1;
      M <<= 2;
      if (tmp >= ttp) // 假设成立
      {
      tmp -= ttp;
      N ++;
      }
      }
      return N;
      }
    展开全文
  • 包含多种单片机常用滤波算法。主要包括平均,限幅,中位值及其各种组合滤波算法介绍,并包含C代码实例。可以直接套用。非常方便。
  • 但在某些特定场合,不可避免地要用到数学运算,尽管单片机并不擅长实现算法和进行复杂的运算。下面主要是介绍如何用单片机实现数字滤波。 在单片机进行数据采集时,会遇到数据的随机误差,随机误差是由随机干扰...
  • 单片机算法FFT

    2015-09-02 19:28:42
    FFT算法的解释
  • 单片机常用算法设计

    2020-07-15 23:31:26
    新手学习单片机必备使用资料,网上牛人分享;单片机常用算法设计详细总结
  • 本文件为本人经过测试的能够直接应用于8位单片机的sha1-hash算法源码,解决了以往在PC机上实现或32位编译器实现的sha1算法无法兼容低端处理器的问题。
  • 帮助你理解PID的两种算法 位置式PID 增量式PID 里面有2个例子
  • 本文主要是分享资料,讲解不会太多,因为分享的资料里面就有具体的详细解析,而且百度上面也有详细的资料,所以本次博文主要是讲解我用PID算法调温的经验。 PID算法调整温度最大的问题的温度的上升问题以及温度在水...
  • C语言中开平方的算法中要开平方的话,可以在头文件中加#include<math.h>.然后调sqrt(n);函数即可.但在单片机中要开平方.可以用到下面算法: 算法1: 本算法只采用移位、加减法、判断和循环实现,因为它不需要...
  • 单片机PID算法程序

    2013-10-10 22:05:05
    转自:... 比例,积分,微分的线性组合,构成控制量u(t),称为:比例(Proportional)、积分(Integrating)、微分(Differentiation)控制,简称PID控制 ... 在实际应用中,可以根据受控
  • 这是一个keil软件的工程代码,实现的是PID算法,AD检测,USART打印。
  • 51单片机学习笔记———8.点亮流水灯的一种奇葩算法 最近学习51单片机的过程发现了一种脑洞大开点亮流水灯的方法,于此分享一下源码: #include<reg52.h> sbit LED0 = P0^0; sbit LED1 = P0^1; sbit LED2 = P0...
  • 本博客主要介绍将RSA加密算法应用到单片机编码之中的一种方法。借助单片机矩阵键盘和显示屏模块进行输入和输出操作。因本方案已经应用到省创项目中,再次只重点介绍RSA算法的应用在驱动程序中的编码以及主函数编码。...
  • 原文地址:FFT算法单片机中的使用&&LCD12864驱动作者:元大帝 好久没更新博客了,觉得对不起自己建立博客的初衷。我这个人太懒了,又没有坚持下去的决心,唉~    言归正传,本次创新基金我是要做一个简易的频谱...
  • 51单片机pid算法

    2020-06-24 16:33:49
    用Pid算法进行温度控制,可自行调节温度输出量,用于51单片机学习
1 2 3 4 5 ... 20
收藏数 18,644
精华内容 7,457
关键字:

单片机算法