精华内容
下载资源
问答
  • 模糊控制原理

    万次阅读 2016-10-26 16:22:38
    模糊控制原理(转载) 我们通常可以用“模糊计算”笼统地代表诸如模糊推理(FIS,Fuzzy Inference System)、模糊逻辑(Fuzzy Logic)、模糊系统等模糊应用领域中所用到的计算方法及理论。在这些系统中,广泛地...

    模糊控制原理(转载)


    我们通常可以用“模糊计算”笼统地代表诸如模糊推理(FIS,Fuzzy Inference System)、模糊逻辑(Fuzzy Logic)、模糊系统等模糊应用领域中所用到的计算方法及理论。在这些系统中,广泛地应用了模糊集理论,并揉和了人工智能的其他手段,因此模糊计算也常常与人工智能相联系。由于模糊计算方法可以表现事物本身性质的内在不确定性,因此它可以模拟人脑认识客观世界的非精确、非线性的信息处理能力。亦此亦彼的模糊逻辑 

    美国西佛罗里达大学的詹姆斯教授曾举过一个鲜明的例子。假如你不幸在沙漠迷了路,而且几天没喝过水,这时你见到两瓶水,其中一瓶贴有标签:“纯净水概率是0.91”,另一瓶标着“纯净水的程度是0.91”。你选哪一瓶呢?相信会是后者。因为后者的水虽然不太干净,但肯定没毒,这里的0.91表现的是水的纯净程度而非“是不是纯净水”,而前者则表明有19%的可能不是纯净水。再比如“人到中年”,就是一个模糊事件,人们对“中年”的理解并不是精确的一个岁数。 

    从上边的例子,可以看到模糊逻辑不是二者逻辑——非此即彼的推理,它也不是传统意义的多值逻辑,而是在承认事物隶属真值中间过渡性的同时,还认为事物在形态和类属方面具有亦此亦彼性、模棱两可性——模糊性。正因如此,模糊计算可以处理不精确的模糊输入信息,可以有效降低感官灵敏度和精确度的要求,而且所需要存储空间少,能够抓住信息处理的主要矛盾,保证信息处理的实时性、多功能性和满意性。 

    美国加州大学L.A.Zadeh博士于1965年发表了关于模糊集的论文,首次提出了表达事物模糊性的重要概念——隶属函数。这篇论文把元素对集的隶属度从原来的非0即1推广到可以取区间【0,1】的任何值,这样用隶属度定量地描述论域中元素符合论域概念的程度,就实现了对普通集合的扩展,从而可以用隶属函数表示模糊集。模糊集理论构成了模糊计算系统的基础,人们在此基础上把人工智能中关于知识表示和推理的方法引入进来,或者说把模糊集理论用到知识工程中去就形成了模糊逻辑和模糊推理;为了克服这些模糊系统知识获取的不足及学习能力低下的缺点,又把神经计算加入到这些模糊系统中,形成了模糊神经系统。这些研究都成为人工智能研究的热点,因为它们表现出了许多领域专家才具有的能力。同时,这些模糊系统在计算形式上一般都以数值计算为主,也通常被人们归为软计算、智能计算的范畴。 

    模糊计算的实战应用 

    模糊计算在应用上可是一点都不含糊,其应用范围非常广泛,它在家电产品中的应用已被人们所接受,例如,模糊洗衣机、模糊冰箱、模糊相机等。另外,在专家系统、智能控制等许多系统中,模糊计算也都大显身手。究其原因,就在于它的工作方式与人类的认知过程是极为相似的。在这里,笔者结合自己的研究实践,以一个建筑结构选型的专家系统为例,说明模糊推理系统是如何模仿领域专家的思维进行工作的,其中所用到的步骤、计算过程在其他模糊系统中也具有典型的代表性。 

    FIS的系统构成与工作原理 

    此主题相关图片如下:


    模糊推理系统的基本结构由四个重要部件组成(见图1):知识库、推理机制、模糊化输入接口与去模糊化输出接口。知识库又包含模糊if-then规则库和数据库。规则库(rule base)中的模糊规则定义和体现了与领域问题有关的专家经验或知识,而数据库则定义模糊规则中用到的隶属函数。模糊规则的形式一般为if A is a then B is b,其中A与B都是语言变量(linguistic variable)而a和b则是由隶属函数映射到的语言值(linguistic values)。例如“if H 很适应 then 结构 很合理”这样一条模糊规则中,建筑高度“H”与“结构”都是语言变量,而“很适应”与“很合理”分别是它们的语言值,在数据库中都有相应的隶属函数加以定义。推理机制(decision-making unit)按照这些规则和所给的事实(例如针对某一拟定方案)执行推理过程,求得合理的输出或结论(例如方案的评价值)。模糊输入接口(fuzzification interface)将明确的输入转换为对应隶属函数的模糊语言值,而去模糊输出接口则将模糊的计算结果转换为明确的输出。 

    由图1我们可以看到,FIS的建立分为三个步骤:一是挑选能够反映系统工作机制的控制输入输出变量 ;二是挑选这些变量的模糊子集;三是用模糊规则建立输出集与输入集的关系。而模糊系统F用三个步骤将输入x映射到输出F(x)。第一步是将输入x并联地匹配到所有“如果部分”的模糊集合,这一步依据输入x属于每一个“如果部分”集合A的程度来“激活”或“启动”模糊规则。第二步是叠加所有按比例收缩的“则部分”集合,生成最终的输出集合。第三步是去模糊化,系统计算出最终输出集的形心或重心作为输出F(x),常用的去模糊化方法有:面积中心法、面积等分法、极值法等。 

    FIS的推理机制 

    此主题相关图片如下:




    我们以对建筑设计高度是否适应所选的结构形式这一单因素评价为例看看系统的推理过程(见图2)。对上述的“if H 很适应 then 结构 很合理”规则而言,我们可以把H看作模糊单点,与“很适应”求交运算,得到H的“很适应”程度,亦即该规则前件的支持程度,再与“很合理”求交运算得到的模糊集便衡量了该关系得以成立的权重。通俗而言,即“H 很适应”得到多大程度的支持,则结构亦在多大程度上“很合理”,整个模糊推理过程如图2所示。例如语言变量x经过A1所代表的语言值程度隶属函数,得到x对A1的隶属度,再与y对B1的隶属度进行求交或求并运算,即可以得到第一个规则前件得以支撑的程度,然后再与后件语言值C1求交,得到该规则成立的权重,同样也可以得到其他规则的权重。经过面积中心法去模糊后就可以得到推论的结果。 

    FIS对结构方案的评价 

    以高层建筑结构的高度适应性评价为例,可以定义拟定方案为U={Uk}={框架结构,框架—剪力墙结构,框架—筒体结构,剪力墙结构,筒体结构},定义各结构型式高度适应性等级论域V={Vi}={V1,V2,V3,V4,V5}为V={很适应,较适应,一般,较不适应,很不适应}。如图3采用高斯型参数化隶属函数—MF(Membership Function),由1~5分别定义这些模糊集合,MF 6、MF 7分别定义的是高度太低而不适应和高度太高而不适应两个集合,此时这两个集合已褪化成了精确集合。 


    此主题相关图片如下:



    对于第K类结构从1~5的模糊集隶属函数可以写成: 

    此主题相关图片如下:




    [HK]为第K类结构在6度设防时的高度限制,I为设计设防烈度,由式(2)就可以得到当设防烈度为I,设计高度为H时,对第K类结构的适应性评价,从而完成从输入到模糊if-then规则模糊化进程,进而确定规则得以支持的程度。 

    此主题相关图片如下:





    以剪力墙结构的高度适应性评价为例,图4给出了剪力墙结构(k=4)设防烈度为7(I=7)时高度适应性评价 的模糊推理过程。图中左侧1~5共5个函数是当k=4时式(1)所代表的从很适应到很不适应5个模糊集的隶属函数。函数6与函数7分别是太低、太高两个不适应情况的模糊集。 

    此时输入input=100m,我们可以看到当设计高度为100米时,剪力墙结构很适应(函数1)的程度,不如较适应(函数2)程度高,而其他模糊集上的隶属度都为零。这表明100m的设计高度采用剪力墙结构较合理的成分大一些,但不是最合理的高度。相应地经过模糊推理后在右侧分别可以看到采用该类结构(本例是剪力墙结构)从合理到不合理得到支持的不同程度。采用面积中心法去模糊后,可得该设计高度下,该类结构合理性的评价值,此时为0.761。同样,我们可以对不同设防烈度下,其它结构类型做出评判,然后从中挑选出合理性最佳的结构型式。 

    案例结论 

    对既定方案进行科学评价是结构选型做到科学决策的一个必然环节,这里通过采用模糊推理系统(FIS)对建筑结构在设计条件下的合理性做出了科学评价,当然其可靠性将取决于专家经验的质量。所构造的FIS以人类自然语言形式推理,体系易于实现、维护,弥补了传统综合评判方法的不足,同时又可以对结构选型设计中存在的大量不确定性信息予以量化考虑,成为量化专家经验的理想途径,它直接实现了专家头脑中从设计输入到性能评价的非线性映射关系,成为方案设计模糊专家系统的一种有效方法。 

    应用“海阔天宽” 

    模糊计算方法以模糊集理论为基础,它有诸如模糊信息检索、模糊识别、模糊聚类等许多广泛的应用,而且由于其采用的方法也是人类大脑所采用的认知方法,因而在社会学方面也大有用武之地。人脑也正是采用模糊的手段,极大地压缩了信息的输入量、处理量、存储量,才得以满意地处理所面临的各种问题。 

    但是从上文的介绍中,我们可以看到神经计算具有学习能力,同时也具有联想、记忆的功能,但是它的知识表达不是显性的,而是隐含在众多的神经联接强度中。而模糊计算正相反,它的学习能力较差,知识与经验的获取很多要靠人来完成,但它的知识表达明确,贴近自然。事实上,真正实用的系统常常把二者合而为一、互用所长、互补所短。无论是用模糊逻辑增强的ANN,还是用ANN增强的模糊逻辑系统,都可以使系统既具有开放的自学习、自适应能力,又具有令人满意的规则推理过程及结论。最近还有一种研究趋势,将几个BP神经网络组合在一起来实现模糊规则及推理过程。 

    可以预见,在未来,模糊计算还会有更大的发展,其应用也会越来越多,最终它将在高级智能系统中发挥不可或缺的作用。


    展开全文
  • Java JDK 动态代理(AOP)使用及实现原理分析

    万次阅读 多人点赞 2019-05-08 21:28:06
    代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。 代理模式UML图: 简单结构...

    目录

    一、什么是代理?

    二、Java 动态代理类 

    三、JDK的动态代理怎么使用?

    四、动态代理怎么实现的?

    五、结论


    一、什么是代理?

    代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

    代理模式UML图:

    简单结构示意图:

    为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

    二、Java 动态代理类 

    Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:

    (1)Interface InvocationHandler:该接口中仅定义了一个方法

    public object invoke(Object obj,Method method, Object[] args)

    在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。

    (2)Proxy:该类即为动态代理类,其中主要包含以下内容:

    protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。

    static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

    static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)

    所谓DynamicProxy是这样一种class:它是在运行时生成的class在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个DynamicProxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作

    在使用动态代理类时,我们必须实现InvocationHandler接口

    通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject)也可以动态改变,从而实现了非常灵活的动态代理关系。

    动态代理步骤
    1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
    2.创建被代理的类以及接口
    3.通过Proxy的静态方法
    newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
    4.通过代理调用方法

    三、JDK的动态代理怎么使用?

    1、需要动态代理的接口:

    package jiankunking;
    
    /**
     * 需要动态代理的接口
     */
    public interface Subject
    {
        /**
         * 你好
         *
         * @param name
         * @return
         */
        public String SayHello(String name);
    
        /**
         * 再见
         *
         * @return
         */
        public String SayGoodBye();
    }

    2、需要代理的实际对象

    package jiankunking;
    
    /**
     * 实际对象
     */
    public class RealSubject implements Subject
    {
    
        /**
         * 你好
         *
         * @param name
         * @return
         */
        public String SayHello(String name)
        {
            return "hello " + name;
        }
    
        /**
         * 再见
         *
         * @return
         */
        public String SayGoodBye()
        {
            return " good bye ";
        }
    }

    3、调用处理器实现类(有木有感觉这里就是传说中的AOP啊)

    package jiankunking;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    
    /**
     * 调用处理器实现类
     * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
     */
    public class InvocationHandlerImpl implements InvocationHandler
    {
    
        /**
         * 这个就是我们要代理的真实对象
         */
        private Object subject;
    
        /**
         * 构造方法,给我们要代理的真实对象赋初值
         *
         * @param subject
         */
        public InvocationHandlerImpl(Object subject)
        {
            this.subject = subject;
        }
    
        /**
         * 该方法负责集中处理动态代理类上的所有方法调用。
         * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
         *
         * @param proxy  代理类实例
         * @param method 被调用的方法对象
         * @param args   调用参数
         * @return
         * @throws Throwable
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            //在代理真实对象前我们可以添加一些自己的操作
            System.out.println("在调用之前,我要干点啥呢?");
    
            System.out.println("Method:" + method);
    
            //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            Object returnValue = method.invoke(subject, args);
    
            //在代理真实对象后我们也可以添加一些自己的操作
            System.out.println("在调用之后,我要干点啥呢?");
    
            return returnValue;
        }
    }

    4、测试

    package jiankunking;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    /**
     * 动态代理演示
     */
    public class DynamicProxyDemonstration
    {
        public static void main(String[] args)
        {
            //代理的真实对象
            Subject realSubject = new RealSubject();
            
            /**
             * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
             * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
             * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
             */
            InvocationHandler handler = new InvocationHandlerImpl(realSubject);
    
            ClassLoader loader = realSubject.getClass().getClassLoader();
            Class[] interfaces = realSubject.getClass().getInterfaces();
            /**
             * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
             */
            Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
    
            System.out.println("动态代理对象的类型:"+subject.getClass().getName());
    
            String hello = subject.SayHello("jiankunking");
            System.out.println(hello);
    //        String goodbye = subject.SayGoodBye();
    //        System.out.println(goodbye);
        }
    
    }

    5、输出结果如下:

    演示demo下载地址:http://download.csdn.net/detail/xunzaosiyecao/9597388

    四、动态代理怎么实现的?

    从使用代码中可以看出,关键点在:

    Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);

    通过跟踪提示代码可以看出:当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用。

    也就是说,当代码执行到:

    subject.SayHello("jiankunking")这句话时,会自动调用InvocationHandlerImpl的invoke方法。这是为啥呢?

    ==============横线之间的是代码跟分析的过程,不想看的朋友可以直接看结论===================

    以下代码来自:JDK1.8.0_92

    既然生成代理对象是用的Proxy类的静态方newProxyInstance,那么我们就去它的源码里看一下它到底都做了些什么? 

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive 
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
    		//检查h 不为空,否则抛异常
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * 获得与指定类装载器和一组接口相关的代理类类型对象
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * 通过反射获取构造函数对象并生成代理类实例
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    			//获取代理对象的构造方法(也就是$Proxy0(InvocationHandler h)) 
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
    			//生成代理类的实例并把InvocationHandlerImpl的实例传给它的构造方法
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }

    我们再进去getProxyClass0方法看一下:

     /**
         * Generate a proxy class.  Must call the checkProxyAccess method
         * to perform permission checks before calling this.
         */
        private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            // If the proxy class defined by the given loader implementing
            // the given interfaces exists, this will simply return the cached copy;
            // otherwise, it will create the proxy class via the ProxyClassFactory
            return proxyClassCache.get(loader, interfaces);
        }

    真相还是没有来到,继续,看一下 proxyClassCache

    /**
         * a cache of proxy classes
         */
        private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    奥,原来用了一下缓存啊

    那么它对应的get方法啥样呢?

     /**
         * Look-up the value through the cache. This always evaluates the
         * {@code subKeyFactory} function and optionally evaluates
         * {@code valueFactory} function if there is no entry in the cache for given
         * pair of (key, subKey) or the entry has already been cleared.
         *
         * @param key       possibly null key
         * @param parameter parameter used together with key to create sub-key and
         *                  value (should not be null)
         * @return the cached value (never null)
         * @throws NullPointerException if {@code parameter} passed in or
         *                              {@code sub-key} calculated by
         *                              {@code subKeyFactory} or {@code value}
         *                              calculated by {@code valueFactory} is null.
         */
        public V get(K key, P parameter) {
            Objects.requireNonNull(parameter);
    
            expungeStaleEntries();
    
            Object cacheKey = CacheKey.valueOf(key, refQueue);
    
            // lazily install the 2nd level valuesMap for the particular cacheKey
            ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
            if (valuesMap == null) {
    			//putIfAbsent这个方法在key不存在的时候加入一个值,如果key存在就不放入
                ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                                      valuesMap = new ConcurrentHashMap<>());
                if (oldValuesMap != null) {
                    valuesMap = oldValuesMap;
                }
            }
    
            // create subKey and retrieve the possible Supplier<V> stored by that
            // subKey from valuesMap
            Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
            Supplier<V> supplier = valuesMap.get(subKey);
            Factory factory = null;
    
            while (true) {
                if (supplier != null) {
                    // supplier might be a Factory or a CacheValue<V> instance
                    V value = supplier.get();
                    if (value != null) {
                        return value;
                    }
                }
                // else no supplier in cache
                // or a supplier that returned null (could be a cleared CacheValue
                // or a Factory that wasn't successful in installing the CacheValue)
    
                // lazily construct a Factory
                if (factory == null) {
                    factory = new Factory(key, parameter, subKey, valuesMap);
                }
    
                if (supplier == null) {				
                    supplier = valuesMap.putIfAbsent(subKey, factory);
                    if (supplier == null) {
                        // successfully installed Factory
                        supplier = factory;
                    }
                    // else retry with winning supplier
                } else {
                    if (valuesMap.replace(subKey, supplier, factory)) {
                        // successfully replaced
                        // cleared CacheEntry / unsuccessful Factory
                        // with our Factory
                        supplier = factory;
                    } else {
                        // retry with current supplier
                        supplier = valuesMap.get(subKey);
                    }
                }
            }
        }

    我们可以看到它调用了 supplier.get(); 获取动态代理类,其中supplier是Factory,这个类定义在WeakCach的内部。

    来瞅瞅,get里面又做了什么?

     public synchronized V get() { // serialize access
                // re-check
                Supplier<V> supplier = valuesMap.get(subKey);
                if (supplier != this) {
                    // something changed while we were waiting:
                    // might be that we were replaced by a CacheValue
                    // or were removed because of failure ->
                    // return null to signal WeakCache.get() to retry
                    // the loop
                    return null;
                }
                // else still us (supplier == this)
    
                // create new value
                V value = null;
                try {
                    value = Objects.requireNonNull(valueFactory.apply(key, parameter));
                } finally {
                    if (value == null) { // remove us on failure
                        valuesMap.remove(subKey, this);
                    }
                }
                // the only path to reach here is with non-null value
                assert value != null;
    
                // wrap value with CacheValue (WeakReference)
                CacheValue<V> cacheValue = new CacheValue<>(value);
    
                // try replacing us with CacheValue (this should always succeed)
                if (valuesMap.replace(subKey, this, cacheValue)) {
                    // put also in reverseMap
                    reverseMap.put(cacheValue, Boolean.TRUE);
                } else {
                    throw new AssertionError("Should not reach here");
                }
    
                // successfully replaced us with new CacheValue -> return the value
                // wrapped by it
                return value;
            }
        }

    发现重点还是木有出现,但我们可以看到它调用了valueFactory.apply(key, parameter)方法:

     /**
         * A factory function that generates, defines and returns the proxy class given
         * the ClassLoader and array of interfaces.
         */
        private static final class ProxyClassFactory
            implements BiFunction<ClassLoader, Class<?>[], Class<?>>
        {
            // prefix for all proxy class names
            private static final String proxyClassNamePrefix = "$Proxy";
    
            // next number to use for generation of unique proxy class names
            private static final AtomicLong nextUniqueNumber = new AtomicLong();
    
            @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
                for (Class<?> intf : interfaces) {
                    /*
                     * Verify that the class loader resolves the name of this
                     * interface to the same Class object.
                     */
                    Class<?> interfaceClass = null;
                    try {
                        interfaceClass = Class.forName(intf.getName(), false, loader);
                    } catch (ClassNotFoundException e) {
                    }
                    if (interfaceClass != intf) {
                        throw new IllegalArgumentException(
                            intf + " is not visible from class loader");
                    }
                    /*
                     * Verify that the Class object actually represents an
                     * interface.
                     */
                    if (!interfaceClass.isInterface()) {
                        throw new IllegalArgumentException(
                            interfaceClass.getName() + " is not an interface");
                    }
                    /*
                     * Verify that this interface is not a duplicate.
                     */
                    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                        throw new IllegalArgumentException(
                            "repeated interface: " + interfaceClass.getName());
                    }
                }
    
                String proxyPkg = null;     // package to define proxy class in
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
                /*
                 * Record the package of a non-public proxy interface so that the
                 * proxy class will be defined in the same package.  Verify that
                 * all non-public proxy interfaces are in the same package.
                 */
                for (Class<?> intf : interfaces) {
                    int flags = intf.getModifiers();
                    if (!Modifier.isPublic(flags)) {
                        accessFlags = Modifier.FINAL;
                        String name = intf.getName();
                        int n = name.lastIndexOf('.');
                        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                        if (proxyPkg == null) {
                            proxyPkg = pkg;
                        } else if (!pkg.equals(proxyPkg)) {
                            throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                        }
                    }
                }
    
                if (proxyPkg == null) {
                    // if no non-public proxy interfaces, use com.sun.proxy package
                    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
                }
    
                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                /*
                 * Generate the specified proxy class.
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    /*
                     * A ClassFormatError here means that (barring bugs in the
                     * proxy class generation code) there was some other
                     * invalid aspect of the arguments supplied to the proxy
                     * class creation (such as virtual machine limitations
                     * exceeded).
                     */
                    throw new IllegalArgumentException(e.toString());
                }
            }
        }

    通过看代码终于找到了重点:

    //生成字节码
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

    那么接下来我们也使用测试一下,使用这个方法生成的字节码是个什么样子:

    package jiankunking;
    
    import sun.misc.ProxyGenerator;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    /**
     * 动态代理演示
     */
    public class DynamicProxyDemonstration
    {
        public static void main(String[] args)
        {
            //代理的真实对象
            Subject realSubject = new RealSubject();
    
            /**
             * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
             * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
             * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
             */
            InvocationHandler handler = new InvocationHandlerImpl(realSubject);
    
            ClassLoader loader = handler.getClass().getClassLoader();
            Class[] interfaces = realSubject.getClass().getInterfaces();
            /**
             * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
             */
            Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
    
            System.out.println("动态代理对象的类型:"+subject.getClass().getName());
    
            String hello = subject.SayHello("jiankunking");
            System.out.println(hello);
            // 将生成的字节码保存到本地,
            createProxyClassFile();
        }
        private static void createProxyClassFile(){
            String name = "ProxySubject";
            byte[] data = ProxyGenerator.generateProxyClass(name,new Class[]{Subject.class});
            FileOutputStream out =null;
            try {
                out = new FileOutputStream(name+".class");
                System.out.println((new File("hello")).getAbsolutePath());
                out.write(data);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(null!=out) try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }

    可以看一下这里代理对象的类型:

    我们用jd-jui 工具将生成的字节码反编译:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    import jiankunking.Subject;
    
    public final class ProxySubject
      extends Proxy
      implements Subject
    {
      private static Method m1;
      private static Method m3;
      private static Method m4;
      private static Method m2;
      private static Method m0;
      
      public ProxySubject(InvocationHandler paramInvocationHandler)
      {
        super(paramInvocationHandler);
      }
      
      public final boolean equals(Object paramObject)
      {
        try
        {
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
      
      public final String SayGoodBye()
      {
        try
        {
          return (String)this.h.invoke(this, m3, null);
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
      
      public final String SayHello(String paramString)
      {
        try
        {
          return (String)this.h.invoke(this, m4, new Object[] { paramString });
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
      
      public final String toString()
      {
        try
        {
          return (String)this.h.invoke(this, m2, null);
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
      
      public final int hashCode()
      {
        try
        {
          return ((Integer)this.h.invoke(this, m0, null)).intValue();
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
      
      static
      {
        try
        {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
          m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
      }
    }

    这就是最终真正的代理类,它继承自Proxy并实现了我们定义的Subject接口,也就是说:

    Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);

    这里的subject实际是这个类的一个实例,那么我们调用它的:

    public final String SayHello(String paramString)

    就是调用我们定义的InvocationHandlerImpl的 invoke方法:

    ======================横线之间的是代码跟分析的过程,不想看的朋友可以直接看结论=====================================

    五、结论

    到了这里,终于解答了:
    subject.SayHello("jiankunking")这句话时,为什么会自动调用InvocationHandlerImpl的invoke方法?
    因为JDK生成的最终真正的代理类,它继承自Proxy并实现了我们定义的Subject接口,
    在实现Subject接口方法的内部,通过反射调用了InvocationHandlerImpl的invoke方法。
    包含生成本地class文件的demo:
    http://download.csdn.net/detail/xunzaosiyecao/9597474
    https://github.com/JianKunKing/DynamicProxyDemo
    通过分析代码可以看出Java 动态代理,具体有如下四步骤:

    1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
    2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    个人微信公众号:

    作者:jiankunking 出处:http://blog.csdn.net/jiankunking

    本文参考过:

    http://www.2cto.com/kf/201608/533663.html

    http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html

     

    展开全文
  • 以前读书的时候学习自动控制原理,就是为了考试,各种相频幅频特性题咣咣做,一点都不含糊,但是实际代表什么意义一点都不知道。现在真是发现,这个东西有用得一批。这篇文章从一阶惯性环节为切入点,对自动控制原理...

    以前读书的时候学习自动控制原理,就是为了考试,各种相频幅频特性题咣咣做,一点都不含糊,但是实际代表什么意义一点都不知道。现在真是发现,这个东西有用得一批。这篇文章从一阶惯性环节为切入点,对自动控制原理进行一个简单的复习。还蛮喜欢博客里面写东西的,按照自己思路,按照逻辑一点一点往下,不像发文章八股文一样。

    1 一阶惯性环节的bode图

    对于这个非常常见的一阶惯性系统而言,其关键指标就是截止频率。

    转折频率:s系数前面的倒数,分母一定是 n*s + 1 的形式。 

    截止频率的定义:从频域响应的角度讲,当保持输入信号的幅度不变,改变频率使输出信号降至最大值的0.707倍,即用频响特性来表述即为-3dB点处即为截止频率。通俗的的讲就是幅频特性-3dB的点和相频特性滞后45°(-45°)的点。

    我们具体的看看不同频率的输入在经过以上一个一阶惯性环节之后的效果。如下面四张图所示,截止频率以前,1rad/s时输出信号的保真度较高,基本能够实现跟随;在截止频率10rad/s处幅值降至0.707,相位滞后45°;100rad/s时,幅值降至0.173,相位滞后将近80°;1000rad/s基本已经没有响应了。证明截止频率对输入信号的响应性能能够提供直接指标。

    2 开环增益与截止频率对bode图的影响

    2.1 更改转折频率

    更改s的系数之后,观察一阶惯性环接的相幅特性,可以看到,截止频率随着系数减小在逐渐右移,右移也代表着转折频率逐渐增大。其实这个地方我们可以把s前面的系数理解为采样时间,其实采样时间越小,采样频率越高,这个可以跟随的频率也就更高,这也是为什么高采样率的系统,高控制频率的系统他的性能更好的原因。可能0.01就可以能够很好跟随10rad/s的输入,但是如果是0.00001就能够更好的跟随了。这也是这么多年FPGA能够逐渐占领市场的原因。

    2.1 更改开环增益

    上图中一直都是按照开关增益为1来进行实验的,现在看看开环增益对相幅特性的影响。这张图蓝色的线是s系数为0.01,也就是转折频率为100的曲线,其他四个是转折频率为10的曲线。观察下图可以得到一下两个结论:

    1、增大一阶惯性环节的开环增益,会导致幅频曲线上移,导致幅频曲线与横轴0的交点右移,也就是截止频率wc增大。

    2、增大一阶惯性环节的开环增益,不会对相频曲线产生任何影响。相频曲线只和s前的系数有关,只和转折频率有关。

    从以上的分析我们可以得到结论:

    1、截止频率对于一阶惯性而言,意味着信号响应性能的转折点,截止频率以前均能够较好的跟随,但是截止频率之后,输入信号被大幅度衰减。

    2、伯德图能够对系统的响应特性进行一个直观的分析。(搞数学的真厉害啊)

    3、增大一阶惯性环节的开环增益,会导致幅频曲线上移,导致幅频曲线与横轴0的交点右移,也就是截止频率wc增大。

    4、增大一阶惯性环节的开环增益,不会对相频曲线产生任何影响。相频曲线只和s前的系数有关,只和转折频率有关。

    整理不易,希望大家帮忙点个赞呀~谢谢啦~^_^

    参数整定以及自动控制原理系列文章:

    永磁同步电机矢量控制到无速度传感器控制学习教程(PMSM)(一)

    如何用matlab画bode图——自动控制原理基础补充(一)

    一阶惯性环节的性能分析——自动控制原理基础补充(二)

    二阶系统的性能分析(开环相幅和阶跃响应)——自动控制原理基础补充(三)

    转速环PI参数整定详解(一)——电机传递函数的来源

    转速环PI参数整定详解(二)——转速环各个环节传递函数的来源

    转速环PI参数整定详解(三)——转速环开环传函特性及其整定策略

    展开全文
  • PID控制原理(全干货)

    千次阅读 多人点赞 2021-01-02 01:51:58
    PID控制原理(全干货) 主要内容: 1.常用的控制算法与PID控制算法的异同点; 2.PID控制算法的理论分析 3.基于单片机的PID算法实现 4.PID算法的工程应用的一些注意事项 5.演示板电路分析 6.PID算法C语言实现—基于ARM-...

    主要内容:
    1.常用的控制算法与PID控制算法的异同点;
    2.PID控制算法的理论分析
    3.基于单片机的PID算法实现
    4.PID算法的工程应用的一些注意事项
    5.演示板电路分析
    6.PID算法C语言实现—基于ARM-CortexM3(STM32)的增量式PID温度控制

          一、常用的控制算法:
    

    1.控制系统基本结构
    在这里插入图片描述
    
    控制目的:
    控制的根本目的就是要使控制对象当前的状态值与用户的设定值相同(最大限度的接近)。
    基本思想:
    用户设定值SV与被控制对象当前的值PV两者同时送入由特定硬件电路模型或特定的软件算法组成的控制算法逻辑中,利用不同的控制算法对SV和PV进行分析、判断、处理,从而产生当前应该输出的控制信号OUT,控制信号经过执行机构施加到控制对象上,从而产生预期的控制效果。
    2.常用控制算法:—位式控制
    1).二位式控制算法

    1. 在这里插入图片描述

    特点:
    a.二位式控制算法输出的控制量只有高低2种状态。
    b.执行机构使控制对象要不全额工作,要不就停止工作。当PV低于SV时全额工作,PV大于或等于SV时就彻底停止工作。如果控制对象是一个1000W的加热器,温度不到时就1000W全功率运行,温度达到时就停止工作。
    c.由于环境因素或控制系统传输延时或者控制对象本身的惯性等因素,控制效果往往是PV在SV的上下有一个较大的波动。
    d.在PV接近SV的临界点时,控制输出信号OUT往往在H和L之间频繁转换,导致执行部件的触点频繁开关动作,易产生干扰及缩短执行部件的寿命。

    2).具有回差的二位式控制算法
    在这里插入图片描述
    特点:
    a. 取SV的正负10%左右作为回差调节上下限,
    高于上限才开始输出L,低于下限才开始输出H;
    b.避免了一般二位式控制算法在临界点时执行部件频繁动作。
    c.因为控制对象只有全额运行或不运行两种状态,仍然存在一般二位式控制算法的缺点:PV总是在SV附近波动。
    3).三位式控制算法
    在这里插入图片描述
    特点:在二位式控制的基础上对控制对象的功率分成0功率(停止工作)、半功率、全功率三种情况(即三位)。
    当前值低于设定值一定比例(一般10%)时OUT1和OUT2同时起控制作用,控制对象全功率运行;
    当前值在设定值的正负10%范围时,OUT1单独作用,工作于半功率状态;
    当前值达到或超过设定值时OUT1和OUT2都停止输出,控制对象停止工作。
    相对一般二位式控制算法,三位式算法对控制对象的当前状态值做了简单的分析,并根据不同的当前状态值输出不同的控制信号。能够较好的对输出产生控制效果。

    小结:位式控制的主要特征:

    1.控制算法只关注控制当前的状态值(PV)与设定值之间的差值—二者当前有差值就输出控制信号,二者当前无差值就不输出控制信号。
    2.位式控制算法的输出信号状态单一,只输出了高低两种状态,这两种状态对应着控制对象的工作与不工作----如果是温度控制系统,就是加热器加热与不加热。
    3.由于控制系统自身的延时或者控制对象自身的惯性,位式控制算法只能使控制对象当前的状态值在设定值附件波动,不能很好的跟踪在设定值的附近甚至相等。

         二、PID控制算法
    

    1.PID控制算法的基本思想
    在这里插入图片描述

    
      PID算法是一种具有预见性的控制算法,其核心思想是:
      
      1>. PID算法不但考虑控制对象的当前状态值(现在状态),而且还考虑控制对象过去一段时间的状态值(历史状态)和最近一段时间的状态值变化(预期),由这3方面共同决定当前的输出控制信号;
      2>.PID控制算法的运算结果是一个数,利用这个数来控制被控对象在多种工作状态(比如加热器的多种功率,阀门的多种开度等)工作,一般输出形式为PWM,基本上满足了按需输出控制信号,根据情况随时改变输出的目的。
    
    2.PID算法分析:
       设某控制系统:用户设定值为SV(也就是希望通过PID控制算法使被控制对象的状态值保持在SV的附件)。
       
      1>从系统投入运行开始,控制算法每隔一段时间对被控制对象的状态值进行采样。由此,可得到开机以来由各个采样时间点被控制对象的状态值所形成的数据序列:
       X1,X2, X3, X4, .... Xk-2,Xk-1,Xk
     说明:
       X1:开机以来的第一次采样值
       Xk:  目前的采样值(最近一次的采样值)
      2> 从这个采样值序列中提取出三方面信息:
       ①当前采样值Xk与用户设定值SV之间的差值:Ek
             Ek =Sv - Xk
       分析Ek:
           >0:说明当前状态值未达标
     Ek    =0:说明当前控制状态值正好满足要求
           <0:说明当前状态值已经超标
         结论:Ek反应了控制对象当前值与设定值的偏差程度,可以根据Ek的大小对输出信号OUT进行调整:偏差程度大OUT增大,偏差程度小OUT减小。即输出信号的强弱与当前偏差程度的大小成比例,所以根据Ek的大小来给出控制信号OUT的当前值的算法称为比例控制(Proportion)。用数学模型可以表示为:
           POUT=(Kp*Ek)+ Out0
         Kp:一般称之为比例系数,可以理解为硬件上的放大器(或衰减器),适当选取Kp将当前误差值Ek
    按一定的增益放大或缩小,以提高控制算法的相应速度。
       Out0:是一个常数,目的是为了当Ek为0时,确保输出信号不为0,以不至于在当前值与设定值相等时控制器输出信号OUT为0,系统处于无控制信号的失控状态。
      ②将投入运行以来的各个采样值都与设定值相减,可得到开机以来每个采样时刻的偏差序列数据:
       E1,E2,E3 .....Ek-2,Ek-1,Ek
      说明:
       E1:开机的第一个采样点与设定值的偏差
           E1=SV-X1;
           E2=SV-X2;
           ......
           EK-2=SV-XK-2;
           EK-1=SV-XK-1;
       Ek:  当前的采样值与设定值的偏差
           EK=SV-XK
      分析开机以来的误差序列:
         每个偏差值可能有:>0,<0,=0这三种可能的值,因为从开机到现在,控制算法不断输出控制信号对被控对象进行控制,导致了过去这段时间有时候超标(Ex<0),有些时候未达标(Ex>0),有时候正好满足要求(Ex=0);如果将这些偏差值进行累加求代数和得到Sk,即:
      Sk=E1+E2+E3+.........+Ek-2+Ek-1+Ek
    分析Sk:
    
          >0: 过去大多数时候未达标
    
      Sk  =0:过去控制效果较理想
    
          <0: 过去大多数时候已经超标
    
     结论:1.通过对Sk的分析,可以对控制算法过去的控制效果进行综合评估。体现了控制算法按照原来的方式输出的控制信号导致了现在的控制结果,所以应该利用这个值来对当前要输出的控制信号OUT进行修正,以确保控制对象会在将来的一小段时间尽快达到用户设定的值。
           2.Sk实际上是过去每个时间点的误差相加,与数学上的定积分运算类似,因此根据Sk对输出信号进行调节的算法称积分(integral)算法。所以积分控制的数学模型为:
      IOUT=(kp* ( (1/Ti)  Exdt))+Out0
       
      Kp是一常数,其目的类似硬件上的放大器,用于将Sk放大或衰减;
     Out0是一常数,为了在历史积分偏差值为0时确保系统有一个输出值,避免失控;
      Ti  是积分时间常数,取值越大会导致输出量OUT会越小,可理解为历史上已经很久的误差值都影响了当前的输出信号。取值越小,输出OUT会越强烈,可理解为积分只考虑了最近一段时间的误差。
         实际中,如果系统已经运行“很长”一段时间了,那些早期采样的偏差值可以忽略他们对当前控制的影响,所以应该根据情况选择合理的Ti值方能得到良好的控制效果。
     标题PID控制
    主要内容:1.常用的控制算法与PID控制算法的异同点;2.PID控制算法的理论分析3.基于单片机的PID算法实现4.PID算法的工程应用的一些注意事项5.演示板电路分析6.PID算法C语言实现---基于ARM-CortexM3(STM32)的增量式PID温度控制一、常用的控制算法:1.控制系统的基本结构:
    控制目的:控制的根本目的就是要使控制对象当前的状态值与用户的设定值相同(最大限度的接近)。基本思想:用户设定值SV与被控制对象当前的值PV两者同时送入由特定硬件电路模型或特定的软件算法组成的控制算法逻辑中,利用不同的控制算法对SV和PV进行分析、判断、处理,从而产生当前应该输出的控制信号OUT,控制信号经过执行机构施加到控制对象上,从而产生预期的控制效果。2.常用控制算法:---位式控制 1).二位式控制算法特点:   a.二位式控制算法输出的控制量只有高低2种状态。   b.执行机构使控制对象要不全额工作,要不就停止工作。当PV低于SV时全额工作,PV大于或等于SV时就彻底停止工作。如果控制对象是一个1000W的加热器,温度不到时就1000W全功率运行,温度达到时就停止工作。   c.由于环境因素或控制系统传输延时或者控制对象本身的惯性等因素,控制效果往往是PV在SV的上下有一个较大的波动。  d.在PV接近SV的临界点时,控制输出信号OUT往往在H和L之间频繁转换,导致执行部件的触点频繁开关动作,易产生干扰及缩短执行部件的寿命。2).具有回差的二位式控制算法特点:  a. 取SV的正负10%左右作为回差调节上下限,高于上限才开始输出L,低于下限才开始输出H;  b.避免了一般二位式控制算法在临界点时执行部件频繁动作。  c.因为控制对象只有全额运行或不运行两种状态,仍然存在一般二位式控制算法的缺点:PV总是在SV附近波动。 3).三位式控制算法特点:在二位式控制的基础上对控制对象的功率分成0功率(停止工作)、半功率、全功率三种情况(即三位)。当前值低于设定值一定比例(一般10%)时OUT1和OUT2同时起控制作用,控制对象全功率运行;当前值在设定值的正负10%范围时,OUT1单独作用,工作于半功率状态;当前值达到或超过设定值时OUT1和OUT2都停止输出,控制对象停止工作。相对一般二位式控制算法,三位式算法对控制对象的当前状态值做了简单的分析,并根据不同的当前状态值输出不同的控制信号。能够较好的对输出产生控制效果。小结:位式控制的主要特征:   1.控制算法只关注控制当前的状态值(PV)与设定值之间的差值---二者当前有差值就输出控制信号,二者当前无差值就不输出控制信号。   2.位式控制算法的输出信号状态单一,只输出了高低两种状态,这两种状态对应着控制对象的工作与不工作----如果是温度控制系统,就是加热器加热与不加热。3.由于控制系统自身的延时或者控制对象自身的惯性,位式控制算法只能使控制对象当前的状态值在设定值附件波动,不能很好的跟踪在设定值的附近甚至相等。二、PID控制算法1.PID控制算法的基本思想  PID算法是一种具有预见性的控制算法,其核心思想是:  1>. PID算法不但考虑控制对象的当前状态值(现在状态),而且还考虑控制对象过去一段时间的状态值(历史状态)和最近一段时间的状态值变化(预期),由这3方面共同决定当前的输出控制信号;  2>.PID控制算法的运算结果是一个数,利用这个数来控制被控对象在多种工作状态(比如加热器的多种功率,阀门的多种开度等)工作,一般输出形式为PWM,基本上满足了按需输出控制信号,根据情况随时改变输出的目的。2.PID算法分析:设某控制系统:用户设定值为SV(也就是希望通过PID控制算法使被控制对象的状态值保持在SV的附件)。1>从系统投入运行开始,控制算法每隔一段时间对被控制对象的状态值进行采样。由此,可得到开机以来由各个采样时间点被控制对象的状态值所形成的数据序列:X1,X2, X3, X4, .... Xk-2,Xk-1,Xk说明:X1:开机以来的第一次采样值Xk:  目前的采样值(最近一次的采样值)2> 从这个采样值序列中提取出三方面信息:①当前采样值Xk与用户设定值SV之间的差值:EkEk =Sv-Xk分析Ek:       >0:说明当前状态值未达标Ek    =0:说明当前控制状态值正好满足要求       <0:说明当前状态值已经超标结论:Ek反应了控制对象当前值与设定值的偏差程度,可以根据Ek的大小对输出信号OUT进行调整:偏差程度大OUT增大,偏差程度小OUT减小。即输出信号的强弱与当前偏差程度的大小成比例,所以根据Ek的大小来给出控制信号OUT的当前值的算法称为比例控制(Proportion)。用数学模型可以表示为:POUT=(Kp*Ek)+Out0     Kp:一般称之为比例系数,可以理解为硬件上的放大器(或衰减器),适当选取Kp将当前误差值Ek按一定的增益放大或缩小,以提高控制算法的相应速度。   Out0:是一个常数,目的是为了当Ek为0时,确保输出信号不为0,以不至于在当前值与设定值相等时控制器输出信号OUT为0,系统处于无控制信号的失控状态。②将投入运行以来的各个采样值都与设定值相减,可得到开机以来每个采样时刻的偏差序列数据:   E1,E2,E3 .....Ek-2,Ek-1,Ek说明:   E1:开机的第一个采样点与设定值的偏差       E1=SV-X1;       E2=SV-X2;       ......       EK-2=SV-XK-2;       EK-1=SV-XK-1;   Ek:  当前的采样值与设定值的偏差       EK=SV-XK分析开机以来的误差序列:每个偏差值可能有:>0,<0,=0这三种可能的值,因为从开机到现在,控制算法不断输出控制信号对被控对象进行控制,导致了过去这段时间有时候超标(Ex<0),有些时候未达标(Ex>0),有时候正好满足要求(Ex=0);如果将这些偏差值进行累加求代数和得到Sk,即:Sk=E1+E2+E3+.........+Ek-2+Ek-1+Ek分析Sk:     >0: 过去大多数时候未达标Sk  =0:过去控制效果较理想      <0: 过去大多数时候已经超标结论:1.通过对Sk的分析,可以对控制算法过去的控制效果进行综合评估。体现了控制算法按照原来的方式输出的控制信号导致了现在的控制结果,所以应该利用这个值来对当前要输出的控制信号OUT进行修正,以确保控制对象会在将来的一小段时间尽快达到用户设定的值。       2.Sk实际上是过去每个时间点的误差相加,与数学上的定积分运算类似,因此根据Sk对输出信号进行调节的算法称积分(integral)算法。所以积分控制的数学模型为:IOUT=(kp* ((1/Ti)Exdt))+Out0  Kp是一常数,其目的类似硬件上的放大器,用于将Sk放大或衰减; Out0是一常数,为了在历史积分偏差值为0时确保系统有一个输出值,避免失控;  Ti  是积分时间常数,取值越大会导致输出量OUT会越小,可理解为历史上已经很久的误差值都影响了当前的输出信号。取值越小,输出OUT会越强烈,可理解为积分只考虑了最近一段时间的误差。实际中,如果系统已经运行“很长”一段时间了,那些早期采样的偏差值可以忽略他们对当前控制的影响,所以应该根据情况选择合理的Ti值方能得到良好的控制效果。③最近两次的偏差之差 DkDk=Ek-Ek-1说明:   Ek:当前的偏差   Ek-1: 基于当前的前一个采样时刻的偏差值(即上一次的偏差值);分析Dk:         >0:说明从上一采样时刻到当前误差有增大趋势Dk   =0:说明从上一采样时刻到当前误差平稳         <0:说明从上一采样时刻到当前误差有减小趋势    Dk>0        Dk=0       Dk<0   Ek-1  Ek     Ek-1  Ek    Ek-1 Ek结论: 1. Dk能够说明从上次采样到当前采样的这段时间被控制对象的状态变化趋势,这种变化的趋势很可能会在一定程度上延续到下一个采样时间点,所以可以根据这个变化趋势(Dk的值)对输出信号OUT进行调整,达到提前控制的目的。  2. Dk形如数学上的微分运算,反应了控制对象在一段时间内的变化趋势及变化量,所以利用Dk对控制器输出信号进行调节的算法称为微分(differential)算法。可以用数学模型表达为:DOUT=Kp*(Td(de/dt))+Out0 Kp:为一常数,可理解为硬件上的放大器或衰减器,用于对输出信号OUT的增益进行调整;  Out0:为一常数,为了在Dk为0时确保OUT都有一个稳定的控制值,避免失控。 Td:叫微分时间常数,(犹如硬件上电感器的自感系数)Td越大导致OUT增大,对输出信号产生强烈的影响。3>PID算法的形成1.比例、积分、微分三种算法的优缺点分析:POUT=(Kp*Ek)+ Out0       --比例算法IOUT=kp* ( (1/Ti)  Exdt)+Out0  --积分算法DOUT=Kp*(Td(de/dt))+Out0    --微分算法比例算法: 只考虑控制对象当前误差,当前有误差才输出控制信号,当前没有误差就不输出控制信号,也就是说只要偏差已经产生了比例算法才采取措施进行调整,所以单独的比例算法不可能将控制对象的状态值控制在设定值上,始终在设定值上下波动;但是比例控制反应灵敏,有误差马上就反应到输出。积分算法:考虑了被控制对象的历史误差情况,过去的误差状况参与了当前的输出控制,但是在系统还没有达到目标期间,往往会因为这些历史的误差对当前的控制产生了干扰(即拖后腿),使用不当反而搅乱当前的输出。但是在系统进入稳定状态后,特别是当前值与设定值没有偏差时,积分算法可以根据过去的偏差值输出一个相对稳定的控制信号,以防止产生偏离目标,起到打预防针的效果。微分算法:单纯的考虑了近期的变化率,当系统的偏差趋近于某一个固定值时(变化率为0),微分算法不输出信号对其偏差进行调整,所以微分算法不能单独使用,它只关心偏差的变化速度,不考虑是否有偏差(偏差变化率为0时偏差不一定是0).但是微分算法能获得控制对象近期的变化趋势,它可以协助输出信号尽早的抑制控制对象的变化。可以理解为将要有剧烈变化时就大幅度调整输出信号进行抑制,避免了控制对象的大幅度变化。以上三种算法综合起来产生一个当前的控制量对控制对象进行控制,它们的优缺点互补,即形成经典的PID算法。 2.PID算法数学模型 OUT=POUT+IOUT+DOUT即:OUT=((Kp*Ek)+ Out0)+(kp* ( (1/Ti) Exdt)+Out0)+ (Kp*(Td(de/dt))+Out0)整理该式子得到:将各项的Out0归并为OUT0。OUT=kp(Ek+((1/Ti)Exdt))+(Td(de/dt)))+OUT03.PID算法在单片机中的应用 1)PID算法在单片机中应用时,对积分和微分项可以作近似变换:对于积分项可改写成:nI =1/Ti∑Ek*Tk=0即用过去一段时间的采样点的偏差值的代数和的代替积分。 T是采样周期,也叫控制周期,每隔T时间段进行一次PID计算。对于微分项可改写成:D=TD*((Ek-Ek-1)/T) Ek:本次偏差,Ek-1上次的偏差值2)位置式PID算法数学模型由此可得到单片机中PID算法的表达式:OUT=kp(Ek+((1/Ti)Exdt))+(Td(de/dt)))+OUT0=>  OUT=             nKp(En+(1/Ti∑Ek*T)+(TD*((Ek-Ek-1)/T)))+out0                 k=0进一步展开得:nOUT=(Kp*Ek) + (Kp*(T/Ti)∑Ek) +(Kp*(TD/T)(EK-Ek-1)) +OUT0k=0令 Ki= Kp*(T/Ti);KD=(Kp*(TD/T)故:nOUT=(Kp*Ek) + (Ki∑Ek) +(KD(EK-Ek-1)) +OUT0k=0程序设计时利用C语言或汇编语言可以方便实现这个计算公式。OUT即为本次运算的结果,利用OUT可以去驱动执行机构输出对应的控制信号,例如温度控制就可以控制PWM的宽度,电磁阀就可以改变电磁线圈电流以改变阀门开度,或者是可控硅的导通角度等;这种PID算法计算出的结果(OUT值)表示当前控制器应该输出的控制量,所以称为位置式(直接输出了执行机构应该达到的状态值)。3)增量式PID算法位置式PID算法计算量较大,比较消耗处理器的资源。在有些控制系统中,执行机构本身没有记忆功能,比如MOS管是否导通完全取决于控制极电压,可控硅是否导通取决于触发信号,继电器是否接通取决于线圈电流等,只要控制信号丢失,执行机构就停止,在这些应用中应该采用位置式PID。也有一些执行机构本身具有记忆功能,比如步进电机,即使控制信号丢失,由于其自身的机械结构会保持在原来的位置等,在这些控制系统中,PID算法没有必要输出本次应该到达的真实位置,只需要说明应该在上次的基础上对输出信号做多大的修正(可正可负)即可,这就是增量式PID算法。增量式PID计算出的是应该在当前控制信号上的调整值,如果计算出为正,则增强输出信号;如果计算出为负则减弱输出信号。增量式PID算法数学模型:如果用OUTK-1表示上次的输出控制信号值,那么当前的输出值应该为OUTk,这两者之间的关系为: OUTK=OUTk-1+   OUTOUT即为应该输出的增量值;上式变形得:OUT= OUTK- OUTk-1本次的位置式算法输出:nOUTk=(Kp*Ek) + (Ki∑Ek) +(KD(EK-Ek-1)) +OUT0 --1式k=0上次的位置式算法输出:n-1OUTk-1=(Kp*Ek-1) +(Ki∑Ek) +(KD(EK-1-Ek-2)) +OUT0--2式k=0上述1式减2式即得到相邻两次的增量:如前所述:Ki= Kp*(T/Ti);KD=(Kp*(TD/T)OUT= OUTK- OUTk-1=kp(EK-EK-1)+((Kp*T)/Ti)Ek+(((Kp*TD)/T)*(Ek-2Ek-1+Ek-2)) EK:本次的偏差; Ek-1:上次的偏差 Ek-2:上上次的偏差 Kp:算法增益调节 Ti :积分时间 TD:  微分时间常数结论:增量式PID的计算只需要最近3次的偏差(本次偏差,上次偏差,上上次偏差),不需要处理器存储大量的历史偏差值,计算量也相对较少,容易实现。 4)关于Ti和TD的理解:在PID控制算法中,当前的输出信号由比例项,积分项,微分项共同作用形成,当比例项输出不为0时,如果积分项对运算输出的贡献作用与比例项对运算对输出的贡献一样时(即同为正或同为负时),积分项相当于重复了一次比例项产生作用的时间,这个时间就可以理解为积分时间。当比例项不为0时,如果微分项在一段时间里计算的结果与比例项对输出的贡献相同(即同为正或同为负)时,微分项相当于在一段时间里重复了比例项的作用,这段时间可理解为就是微分时间。实际应用中应该合理选择Kp,Ti,Td以确保三者对输出的贡献平衡,从而使控制对象在设定值的附近。
    
    
    展开全文
  • TCP流量控制与拥塞控制原理分析

    千次阅读 2019-05-08 23:31:32
    TCP流量控制 使用滑动窗口进行流量控制 所谓的流量控制,就是让发送方的发送速率不要太快,让接收方来得及接收。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。 如图说明了如何利用滑动窗口机制...
  • 自动控制原理 1 1.1 开环与闭环系统 简单的开环系统 闭环系统转换成为闭环系统: 1.2 稳定性分析2 对于一个系统,如果没有稳定性的先决条件,那么其他的(稳态误差分析、瞬态误差分析)将无从说起。稳定性:...
  • 奈氏判据的数学基础 1.1 幅角原理 1.2 复变函数 F(s)F(s)F(s)的选择 1.3 s平面闭合曲线 Γ\GammaΓ的选择 1.4 开环传递函数 G(s)H(s)G(s)H(s)G(s)H(s)闭合曲线的绘制 1.5 闭合曲线 ΓF\Gamma_{F}ΓF​ 包围原点圈数...
  • 谈谈 Java 反射机制,动态代理是基于什么原理?典型回答反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类...
  • 自动控制原理->根轨迹

    千次阅读 2020-11-15 20:48:59
    √×√×√×√√ 根轨迹 根轨迹的基本概念 对于系统的动态参数变化,每变化一次增益,我们就要重新求解一次特征方程,非常地麻烦,非常地不爽,非常地不快乐,那么能不能避免这样的计算呢?可不可以不写特征方程也...
  • 文章目录 极坐标频率特性图(Nyquist 图) 4.5 Nyquist 稳定性...对应于任一 K g K_{g} Kg​ 值,闭环系统极点之和保持不变该结论对于绘制根轨迹草图很有用)。 闭环系统极点之积和开环系统零、极点之间有如下关系:
  • 浅谈两轮平衡车的控制原理(续)

    万次阅读 多人点赞 2018-07-27 11:29:05
    单纯的讲解原理可能会很无聊,但是作为一个技术宅来说,就算头皮发麻也要接着看下去。哈哈,吾理小子争取用通俗的语言把自己懂的知识讲解出来。 好了,闲话少说,进入正题。上文已经做好了平衡车站立起来的全部准备...
  • 庞特里亚金极小值原理是在控制向量u(t)受限制的情况下,使得目标函数J取极小,从而求解最优控制问题的原理和方法,又称极大值原理。λ是协态向量,系统模型有多少个变量就有多少个协态。s和u都是省略了符合t的,代表...
  • 步进电机是将电脉冲信号转变为角位移或线位移以控制转子转动的开环控制电机,它旋转是以固定的角度(步距角)一步一步运行的,故称步进电机。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲...
  • 如果系数矩阵 AAA 存在实部为零的单重特征值,那么其对应的动态响应分量会随着时间的增长而趋于常数值(或等幅振荡)。线性系统理论中通常将这种稳定称为临界稳定。在考虑线性系统(特别是输入-输出模型描述的线性...
  • Linux动态连接原理 GOT PLT表详解

    千次阅读 2013-07-05 13:54:48
    Linux动态连接原理 GOT PLT表详解 注意: 以下所用的连接器是指,ld, 而加载器是指ld-linux.so; 1, GOT表; GOT(Global Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数...
  • 两万+的吐血总结。代理模式(Proxy Pattern)是一个使用频率非常高的设计模式,其定义如下:Provide a surrogate or placeholder for another object to ...以及结合spring和jdk源码分析aop原理,手写jdk动态代理方式。
  • 【TCP/IP详解】【pause】以太网(PAUSE)流量控制原理

    千次阅读 多人点赞 2019-09-30 14:26:44
    以太网 数据链路层 PAUSE帧 流量控制。 摘 要: PAUSE操作实现了一种简单的停-等式流量控制机制,可以防止瞬时过载导致缓冲区溢出时不必要的帧丢失。 以太网流控的引入 硬件成本和数据帧处理速度的限制,缓冲...
  • 在自动控制原理课程中,利用折线式伯德图计算截止频率是很常见的题型,下面介绍两种做法。 对于以下传递函数: G(s)=50s2(s2+s+1)(10s+1)=G1(s)G2(s)G3(s)G4(s)G5(s){G(s)=\frac{50}{s^2(s^2+s+1)(10s+1)}=G_1(s)G_2...
  • 焊接机器人控制系统原理分析

    千次阅读 2017-01-21 15:11:54
    焊接机器人控制系统原理及应用分析焊接是工业生产中非常重要的加工方式,同时由于焊接烟尘、弧光和金属飞溅的存在,焊接的工作环境非常恶劣,随着人工成本的逐步提升,以及人们对焊接质量的精益求精,焊接机器人得到...
  • 舵机分类和控制原理简述(180°模拟电机)SG90 MG996R

    千次阅读 多人点赞 2020-04-02 22:51:38
    参考书目:《我的机器人 仿生机器人的设计与制作》罗庆生 北京理工大学出版社 ...在控制脉冲持续给定的情况下,通过控制电路读阻值并与控制脉冲比较就能调整电机的速度和方向,使电机向指定角度旋转并固定在该角度。
  • 静态和动态控制数码管

    万次阅读 多人点赞 2018-01-02 08:41:29
    1.2 工作原理(1)亮灭原理(其实就是内部的照明LED)。(2)显示数字(甚至文字)原理:利用内部的LED的亮和灭让外部的组成数字的笔画显示或者不显示,人看到的就是不同的数字。1.3 共阳极和共阴极数码管(1)驱动方法的...
  • Pid控制算法—算法原理

    千次阅读 2017-04-10 10:46:12
    一 PID算法原理 此算法的C++实现是依据http://download.csdn.net/detail/hkyshl/8981269?web=web 来修改的,此版本为C语言,而我最近在学C++,所以转换一下。 在工业应用中PID及其衍生算法是应用最广泛的算法之一...
  • 前面的文章讲完了一阶惯性环节的性能,主要对其相幅特性进行了分析,我们得到了几个关键的结论。...其实这里与前面一篇文章的一阶惯性环节的区别就是加了一个积分环节,在自动控制原理这门课里面讲到过系统阶层的
  • 结论: Apollo配置中心动态生效机制,是基于Http长轮询请求和Spring扩展机制实现的,在Spring容器启动过程中,Apollo通过自定义的BeanPostProcessor和BeanFactoryPostProcessor將参数中包含${…}占位符和@Value注解...
  • 谈谈滞后补偿器与PI控制及其原理分析

    千次阅读 多人点赞 2020-08-02 15:47:02
    1.什么是滞后补偿器以及和PI控制的联系 2.PI控制对系统的影响 3.如何设计一个运动控制系统 4.抗积分饱和的方法 5.一个实例 1.什么是超前补偿器以及和PI控制的联系 上一篇文章提到了超前补偿器,超前这个词的含义...
  • 用Mybatis+Spring框架,通过XML的编写和接口的编写,实现数据库的CRUD。...1.得到注入的Spring Bean(这个Bean是Mybatis通过JDK动态代理生成的)。 2.执行Bean方法(通过Bean里面的id找到具体的SQL,并执行)。
  • PID控制算法的C语言实现一 PID算法原理  最近两天在考虑一般控制算法的C语言实现问题,发现网络上尚没有一套完整的比较体系的讲解。于是总结了几天,整理一套思路分享给大家。  在工业应用中PID及其衍生算法是...
  • 线性二次型最优控制器LQR设计原理以及matlab实现

    万次阅读 多人点赞 2019-10-03 11:12:44
    线性二次型最优控制器概述 连续系统线性二次型最优控制 离散系统线性二次型最优控制 线性二次型Gauss最优控制 应用经典控制理论设计控制系统,能够解决很多简单、确定系统的实际设计问题。但是对于诸多新型而复杂的...
  • 继电器控制电路原理解析说明

    万次阅读 多人点赞 2016-11-30 21:27:00
    ,当集成电路控制端为+VCC时,应能至少提供1.2mA的驱动电流(流过R1的电流)给本驱动电路,而许多集成电路(例如标准8051单片机)输出的高电平不能达到这个要求, 但它的低电平驱动能力则比较强(例如标准8051单片机...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 65,870
精华内容 26,348
关键字:

动态控制原理结论