0


一种代码编写方法分享(适用于单片机、PLC、FPGA等可进行过程控制或多状态切换的编程)

前言

    本方法是笔者在总结以往编程经验,并结合实际项目经历的基础上提出的一种代码编写方法,仅作为方法分享。如有不足之处,敬请斧正。

第一步,梳理动作流程

    在正式开始编写代码之前,首先需要根据需求梳理动作流程。

    例如需求文档要求我们做一个流水灯,那么我们首先应该明确到底要做什么效果的流水灯。是顺向还是逆向?流水的速度是多少?每次亮几个灯?需不需要样式的切换等等。

    搞清楚上述问题后还要梳理动作的流程,要能够在脑海中演绎一遍动作效果,做到胸有成竹。

第二步,分解动作

    需求文档中提出的动作可能是一个非常复杂的动作。继续以流水灯为例,需求文档要求的效果可能又有正向流水,也有反向流水,甚至每次流水的花样还不一样。甲方提的需求可能是效果越炫酷越好。这样一来汗流浃背的就是开发人员了。

    我们在面临这种复杂、难以实现的动作时,可以将其分解成一个个容易实现的小动作。炫酷的流水灯不论有多复杂,它终究是由一个个简单的小动作组成的。我们可以把它分解成每一趟需要做的动作,甚至可以更细一步,每次灯变化时需要灭哪几个灯,又要亮哪几个灯。就这样把一个复杂的动作分解成若干个容易实现的小动作。

第三步,分配状态,建立有限状态机

    有限状态机是表示有限个状态以及在这些状态之间的转移和动作等行为的模型。有限状态机是将系统的运行情况分成有限个状态,并通过判断某些特定的条件进行状态的改变。如下图所示。

    其中初始状态指的是系统上电并进行初始化后所处的状态,此时系统什么也不做,等待触发初始条件(可以是开始信号,可以是某个中断信号,也可以是其它由外界输入的信号)。其中状态1~4是系统的四个状态,每个不同的状态系统都会做不同的事。**请注意:状态一定要闭环,否则系统无法持续运行。**

    每个状态的转换都需要判断相应的条件有没有满足,只有满足全部条件后才能进行状态的转换。图中,笔者还从每个状态都引出了一条转换到初始状态的转换路径,这样做的目的是为了进行软复位(当满足一定条件时,系统回到初始状态)。在实际开发中,我们要根据实际项目需求将系统的运行分成若干个状态,状态之间的转换路径也需要根据项目需求决定。

    在实际编程中,我们可以通过循环判断+条件语句的方式建立一个状态机。

    对于单片机和嵌入式开发,可以在while循环中添加switch语句,将状态变量(用于表示状态的编号)作为判断条件。

    对于PLC而言。如果用的是ST语言,可以通过CASE语句实现,同样是将状态变量作为判断条件;如果用的是梯形图,且是用CODESYS开发,则可通过EQ功能块+状态变量的方式实现;如果用的是梯形图,且是用AutoShop软件开发,则可通过CMP指令+数据寄存器D的方式实现。其他的PLC软件笔者没有使用过,请读者自行寻找方法。一般都是比较判断+状态变量的形式。PLC不用考虑循环问题**(此处的循环是指程序的循环执行,不是指状态机的循环!)**,会自动循环扫描网络或程序。

    对于FPGA而言,可以在always语句中通过if语句+状态变量建立状态机。其实在FPGA开发中会经常用到有限状态机。甚至还会用到多种更细致、复杂的状态机模型,如摩尔型状态机、米勒型状态机等。

    经过前两步的工作,我们已将一个复杂的需求动作分解成了若干个简单易实现的小动作,我们可以为每个小动作都分配一个状态值,并通过有限状态机将这些小动作关联起来,通过状态的转换实现小动作的衔接。**使用状态机关联动作,可以让动作的编辑更容易。我们可以通过改变状态的转换路径来改变动作的执行顺序。**

    对于炫酷流水灯这个项目来说,我们可以为每个简单的子动作赋予一个状态值,并通过状态机串联所有动作,通过改变状态的转换路径,可以很轻易地实现流水花样的编辑和改变。**但无论怎么改变,无论转换路径多么复杂,多么炫酷,一定要保证最后能回到第一个状态,这样才能实现流水灯的循环运行。**

** ** 用有限状态机的方法书写代码还有一个好处就是便于排查问题。

    由于我们已将系统的运行分解成了不同的状态。如果系统在运行过程中出了BUG,我们不需要地毯式地排查问题,只需要排查出现BUG的状态即可。这样可以节省许多排查问题的时间,提高工作效率。

第四步,建立标志信号链,让状态机独立于执行程序

    一个小动作的执行往往需要若干行代码来实现,我们可能需要调用很多函数、功能块,如果我们把这些代码全部塞到状态机(Switch语句、CASE语句灯实现状态机的程序)里,那状态机就会变得非常臃肿。出了BUG就算我们能准确找到出现BUG的状态,排查问题也要耗费不少精力。因此我们需要建立一个标志信号链,让状态机独立与执行程序,让状态机只专注于状态的转换和标志信号的给出。这就好比一场战争一样,将军(状态机)不需要上前线冲锋陷阵(执行动作),只需要根据已知信息(目前的状态及满足的条件)发布作战命令(给出标志信号),由手底下的士兵(执行子程序)执行命令,并反馈执行结果(返回标志信号),将军根据反馈的结果判断下一步要做的事(进入下一个状态?执行下一个执行子程序?报错?)。

    状态机需要给出一定的标志信号,这些标志信号是触发执行子程序所需要的信号,子程序执行完后给状态机返回一个标志信号,如果顺利执行完成,就返回一个执行完毕的标志信号,如果执行过程中遇到了错误,则返回相应的标志信号,好让状态机做出合适的决定(是跳转到另一个状态还是执行另一个子程序)。

    在建立标志信号链时,切记不要过于盘根错结,节点不要太多,最好是一去一回(从状态机到子程序,再从子程序到状态机),不要弯弯绕绕,贯穿多个子程序(例如从状态机到子程序A,从子程序A到子程序C,再从子程序C到子程序F,又从子程序F到子程序A,最后才从子程序A回到状态机)。

    如果这样编写程序,当某个状态出现BUG时,我们只需要检查标志信号链的信号传递,即有没有信号传出或者有没有信号传回。如果发现有哪个标志信号链没有正常的信号输入输出,那就说明这条信号标志链所指向的子程序是有问题的,我们只需要排查这个子程序即可。

案例分享:

//复位状态机
CASE F_status OF
    0://初始状态,等待上电
    IF bpowerOn THEN  //启动按键按下,检测使能标志位
        F_status := 1;    
    END_IF
    
    1://横轴回零
        ResetMove_A1_On := TRUE;//功能块的使能标志信号
        ResetMove_A2_On := TRUE;
        ResetMove_A3_On := TRUE;
        IF ResetMove_A1_Done AND ResetMove_A2_Done AND ResetMove_A3_Done THEN
        //功能块的完成标志信号,只有所有功能块都返回完成标志信号才进IF
            F_status := 10;//状态变量改变,下一扫描周期则跳转到下一状态(10状态)
            ResetMove_A1_On := FALSE;//功能块使能标志位复位
            ResetMove_A2_On := FALSE;
            ResetMove_A3_On := FALSE;
        END_IF
                
    10://齿轮回零
        ResetMove_R1_On := TRUE;
        ResetMove_R2_On := TRUE;
        ResetMove_R3_On := TRUE;
        IF ResetMove_R1_Done AND ResetMove_R2_Done AND ResetMove_R3_Done THEN
            ResetMove_R1_On := FALSE;
            ResetMove_R2_On := FALSE;
            ResetMove_R3_On := FALSE;
            F_status := 20;
        END_IF
            
    20://建立横轴坐标系
        ResetPos_A1_On := TRUE;
        ResetPos_A2_On := TRUE;
        ResetPos_A3_On := TRUE;
        IF ResetPos_A1_Done AND ResetPos_A2_Done AND ResetPos_A3_Done THEN
            ResetPos_A1_On :=  FALSE;
            ResetPos_A2_On :=  FALSE;
            ResetPos_A3_On :=  FALSE;
            F_status := 30;
        END_IF
        
    30://齿轮啮合
        EnpMove_A1_On := TRUE;
        EnpMove_A3_On := TRUE;
        IF EnpMove_A1_Done AND EnpMove_A3_Done THEN
            EnpMove_A1_On := FALSE;
            EnpMove_A3_On := FALSE;
            F_status := 40;
        END_IF
        
    40://设置相关标志位,回到初始状态
        IF NOT Debug_Mode THEN
            GVL.bResetDone := TRUE;
            //复位完成标志,给另一状态机发送信号,以开启下一状态机的功能
        END_IF
        bAxisPro_Start := TRUE;//撞轴保护功能开启
        F_status := 0;//复位状态机回到初始状态
        bpowerOn := FALSE;   //将使能标志位复位 
END_CASE

以上代码是笔者最近一个PLC项目的一部分,该项目使用CODESYS开发,使用的是ST语言,这种语言与C语言语法相似。上述代码使用CASE语句写了一个6个状态的状态机,CASE语句语法与C语言的switch语句相似,如下:

CASE 变量 OF 
1:
    代码块

2:
    代码块

...

END_CASE
    由于这个项目比较复杂,一个状态机无法实现所有功能,因此建立了多个状态机,并依靠标志信号链实现状态机的转换。以上代码是专门用于系统复位的复位状态机。

    在这个状态机里,使用F_status作为状态变量,并给几个需要执行的子动作赋予状态值。在每个状态里,只给出功能块(PLC里的函数,用于执行特定动作)的使能标志信号,然后就等待功能块传回完成标志信号。当所有功能块都传回完成信号,状态机才会进行状态的转换。(**注:状态转换前一定要将使能标志位复位,使标志信号链完成闭环,以便于下次进入该状态时重新给出使能信号)**

** **状态设置为10递进的原因:后续可能会在状态之间插入新的状态,10递进是为了给新状态留出空间。

    以上是本文所指的代码编写方法的操作步骤和相关说明,笔者使用这套方法解决了不少代码编写难题。上述方法可能不适合所有应用场景,请根据自己实际的开发场景酌情参考。

本文转载自: https://blog.csdn.net/Jan_wen/article/details/140951876
版权归原作者 Jan_wen 所有, 如有侵权,请联系我们删除。

“一种代码编写方法分享(适用于单片机、PLC、FPGA等可进行过程控制或多状态切换的编程)”的评论:

还没有评论