对家电软件结构的研究和实践

如今,家电越来越普及,智能化程度也越来越高,对软件的质量也相应提出了新的要求。对于一个业内软件开发者,经常要“拷问”自己的问题就是:“我该怎么写才容易改?我该怎么写才不容易出错?”。在软件相关行业中,代码更改几乎总是会发生的,而且可能不是一次两次。造成更改的原因有很多种,如:需求改变、添加/删除功能、软件自身有bug等等。在家电业中,在你对一款产品编写代码时,你应该意识到,它可能还有后续衍生产品,可能会添加或更改一些功能。在业内混过的coder都应该经历过这样的情况:在产品批产前或批产后,发现软件还存在bug,于是加班找bug,加班返工,被老板批,被上司骂,很不舒服。更改在所难免,我们是否可以让“改”变得更容易;bug无法避免,我们是否可以尽量减少它。下面我就谈谈我的一些经验,抛砖引玉。不足之处,不吝赐教。在下面的讨论中,我们谈论的都是家电,不再每次都重复这两个字了。从大的架构上讲,使用的绝大多数都是前后台。将系统要执行的任务放在while循环中,将一些对响应速度要求高的代码放在中断服务程序中。如下所示:
volatile uint8 Flag_SysTick;
int main(void)
{
    static uint8 Process_index;

    while(true)
    {
        if (Flag_SysTick)
        {
            Flag_SysTick = false;

            Task_0();

            Process_index ++;
            switch(Process_index)
            {
                case 1:
                    Task_1();
                    break;
                case 2:
                    Task_2();
                break;
                case 3:
                    Task_3();
                break;
                case 4:
                    Task_4();
                break;
                case 5:
                    Task_5();
                    Process_index = 0;
                break;
                default:
                    Process_index = 0;
                break;
            }
        }
    }
    return 0;
}

void Int_0(void) interrupt 0
{
    Flag_SysTick = true;
}

void Int_1(void) interrupt 1
{
    ...
}

...

对于家电软件来说,这种结构已经足够好了。

在上面这种大的结构上,我们如何去精心安排,得到易写易改不易错的软件呢?

先来举个例子,看看传统方法的不足,再来想想解决办法。

产品往往有多种状态,在各状态下,往往有不同的显示规则,按键响应规则,负载控制规则及时间处理规则。下面是一款洗衣机的各个状态及其描述:

状态

描述

按键响应规则描述

显示规则

负载控制

时间处理

State_Off

关机状态

按启动键,进入设置状态

无显示

关闭所有负载

无时间处理

State_Set

设置状态

10分钟无用户操作,进入关机状态

State_FuzzyCheck

模糊检状态

State_Error

故障状态

State_Service

服务状态

State_VersionSetting

变种设置状态

State_UITest

界面检测状态

State_NoWaterCheck

无水检状态

State_FactoryTest

工厂模式

State_BurnTest

老化模式

State_SpinTest

连脱测试

State_??

后续可能添加


大部分产品规格书都会有上图类似的说明,按状态分章节描述各状态的按键响应规则、显示控制规则、负载控制规则及时间处理规则。按照上表所描述的规则,我们的软件传统上是这样写的:

typedef enum 
{
    STATE_OFF,
    STATE_SET,
    STATE_FUZZY_CHECK,
    STATE_ERROR,
    STATE_SERVICE,
    STATE_VERSION_SET,
    STATE_UI_TEST,
    STATE_NO_WATER_CHECK,
    STATE_FACTORY_TEST,
    STATE_BURN_TEST,
    STATE_SPIN_TEST
}State_t;

State_t System_CurrentState;

/* 按键处理函数 */
void KeyAction_Deal(void)
{
    if (System_CurrentState == STATE_OFF)
    {
        /* 按键处理 */
        if (Key_ActionType == KEY_SHORT)
        {
            if(Key_Code == KEY_POWER)
            {
                ...
            }
            else if (Key_Code == KEY_START)
            {
                ...
            }
        }
        else if (Key_ActionType == KEY_LONG)
        {
            if(Key_Code == KEY_POWER)
            {
                ...
            }
            else if (Key_Code == KEY_START)
            {
                ...
            }
        }
        ...
    }
    else if (System_CurrentState == STATE_SET)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_FUZZY_CHECK)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_ERROR)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_SERVICE)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_VERSION_SET)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_UI_TEST)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_NO_WATER_CHECK)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_FACTORY_TEST)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_BURN_TEST)
    {
        /* 按键处理 */
        ...
        ...
    }
    else if (System_CurrentState == STATE_SPIN_TEST)
    {
        /* 按键处理 */
        ...
        ...
    }
    ...
}

/* 显示控制函数 */
void Display_Ctr(void)
{
    /* 同 KeyAction_Deal函数结构。*/
    ...
}

/* 负载控制函数 */
void Load_Ctr(void)
{
    /* 同 KeyAction_Deal函数结构。*/
    ...
}

/* 时间控制函数 */
void Timer_Ctr(void)
{
    /* 同 KeyAction_Deal函数结构。*/
    ...
}

看看上面的代码,很明显能看出它有以下几点稍显不足:

1.        同样的逻辑判断出现了4次。对当前系统状态的判断,在上面4个处理函数中都有出现。

2.        逻辑嵌套较深。

3.        缺乏灵活性。对某一状态进行修改,都要在庞大的if else中找到对应的分支进行修改。(最好是一个状态一个文件,改这个状态就只打开这个文件。)

4.        单片机系统资源的浪费。要执行第n个分支中的语句,都要经历前面n-1个判断,而且还重复了多次。

实际上,上面那些不足都是可以避免的。具体思想如下(有限状态机概念):

基本思想

各个状态对应有相对独立的.c文件,其中包含该状态自己的按键处理、显示控制、负载控制及时间控制等函数。在运行时,当符合某状态迁移条件时(如,在关机状态按电源键迁移到设置状态,预约状态时间到迁移到工作状态等),将目标状态的各逻辑处理函数放入预先定义好的函数指针变量中。主系统循环只要从函数指针变量中取得对应函数的地址并调用它就可以了。其实挺简单是不是。带来的好处是显而易见的,比如工作状态出现了工作逻辑或显示逻辑问题,我知道问题肯定在State_Work.c中,我打开它并且编辑它,而且我确切知道我不会影响其它任何状态。要添加一个新的状态同样很方便,新建一个.c文件,写入该状态的逻辑处理函数就OK了。没有重复的状态检测,整个结构清爽干净。同时速度也快了些。

这里贴上几个核心文件代码,完整代码请下载附件。

为方便大家调试运行,工程为Visual Studio 2013工程(可执行文件见附件)。移植到你的单片机中应该不难,大家可试试。


工程结构如下:

工程结构


core.h头文件

#ifndef CORE_H
#define CORE_H

typedef void(*task_t)(void);

typedef struct  
{
    task_t keyDeal_Handler;
    task_t displayCtr_Handler;
    task_t loadCtr_Handler;
    task_t timeCtr_Handler;
}State_t;

extern State_t State;

void State_Backup(void);
void State_Recovery(void);

void State_KeyActionDeal(void);
void State_DisplayCtr(void);
void State_LoadCtr(void);
void State_TimeCtr(void);

#endif


core.c文件
#include <stdio.h>

#include "system/system.h"

#include "core.h"

State_t State;

static State_t State_old;

void State_Backup(void)
{
    State_old = State;
}

void State_Recovery(void)
{
    State = State_old;
}

void State_KeyActionDeal(void)
{
    State.keyDeal_Handler();
}

void State_DisplayCtr(void)
{
    printf("---------------------------操作说明---------------------------\n");
    printf("数字小键盘:0 -> 电源    1 -> 加    2 -> 开始    3 -> 减\n");
    printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");

    State.displayCtr_Handler();
}

void State_LoadCtr(void)
{
    System_Load1Enable = FALSE;
    System_Load2Enable = FALSE;
    System_Load3Enable = FALSE;

    State.loadCtr_Handler();
}

void State_TimeCtr(void)
{
    State.timeCtr_Handler();
}

state_work.h

#ifndef STATE_WORK_H
#define STATE_WORK_H

void TransitionTO_StateWork(void);

#endif

state_work.c

#include <stdio.h>

#include "system/system.h"
#include "key/key.h"

#include "FSM/core.h"
#include "FSM/state_off/state_off.h"

#include "state_work.h"

static bool_t isPaused;

static void Key_actionDeal(void)
{
    if (Key_ActionType == KEY_ACTION_SHORT)
    {
        if (Key_Code == KEY_POWER)
        {
            TransitionTO_StateOff();
        }
        else if (Key_Code == KEY_START)
        {
            isPaused = !isPaused;
        }
        printf("\a");
    }
}

static void Display_ctr(void)
{
    printf("======State_Work状态!======\n\n");

    printf("----------操作说明----------\n");

    printf("按 电源键 进入关机状态。\n按 开始键 切换暂停启动。\n工作一分钟结束进入关机状态!\n\n");

    printf("----------负载状态----------\n");

    if (System_Load1Enable)
    {
        printf("负载1:工作\n");
    }
    else
    {
        printf("负载1:不工作\n");
    }

    if (System_Load2Enable)
    {
        printf("负载2:工作\n");
    }
    else
    {
        printf("负载2:不工作\n");
    }

    if (System_Load3Enable)
    {
        printf("负载3:工作\n\n");
    }
    else
    {
        printf("负载3:不工作\n\n");
    }

    printf("----------测试变量值----------\n");
    printf("System_TestCount = %d;\n", System_TestCount);
}

static void Load_ctr(void)
{
    if (!isPaused)
    {
        System_Load1Enable = TRUE;
        System_Load2Enable = TRUE;
        System_Load3Enable = TRUE;
    }
}

static void Time_ctr(void)
{
    if (!isPaused)
    {
        System_TestCount++;
        if (System_TestCount > 600)
        {
            TransitionTO_StateOff();
        }
    }
}

void TransitionTO_StateWork(void)
{
    State.keyDeal_Handler = Key_actionDeal;
    State.displayCtr_Handler = Display_ctr;
    State.loadCtr_Handler = Load_ctr;
    State.timeCtr_Handler = Time_ctr;
}

还有一些类似和通用文件,我就不贴了,看附件。

下面贴几张各状态运行效果图:

关机状态:

关机状态


设置状态:

设置状态


工作状态:

工作状态

采用这种分状态和状态迁移的方式去编码,你会发现你的的思路是清晰的,不会因为状态的过多而引入不必要的复杂度。加一个状态,也就是加一个.c文件而已。

点击打开可执行文件下载地址

点击打开源代码下载地址

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
硬件实验平台的搭建:该设计主要由数据采集模块、控制模块、通信模块等三部分组成,其中数据采集模块包括温湿度采集传感器、空气质量检测传感器,控制模块STM32F103ZET6作为中央控制单元,通信模块包括红外发射模块以及移动通信模块。同时,本设计的软件算法原理主要是基于预测评价指标的最适温度算法及空气质量检测算法实现的。该系统的工作流程为系统上电后进行硬件模块的初始化,并在可以进行人机交互的触摸屏上完成设置,然后便由数据采集模块进行工作,实现空调的智能化控制以及空气质量的报警功能。 软件代码设计思路:本设计以STM32微控制作为核心处理器,利用PMV、热舒适方程设计最适温度算法,同时利用多传感器对室内的家居环境包括空气质量等指标进行实时的监测,然后控制空气净化器的开启并将房间内的环境监测数据利用GPRS技术发送至用户移动端。本设计选用STM32F103ZET6作为核心处理器,选用高性能的SIM800C作为GSM模块完成远程移动通信,该模块通过简单的驱动电路与天线外围电路即可实现无线通信模块与STM32的硬件连接。在环境数据监测方面,选用DHT11温湿度传感器来获取室内环境的实时湿度,选用DS18B20数字温度传感器完成温度数据的采集,为最适温度算法提供输入量。控制器对空调的自动调节是基于红外编码方案实现。具体硬件设计电路包括:电源模块,时钟模块,红外发射模块,温湿度采集模块,空气质量监测模块,和GPRS无线通信模块。首先进行对室内的环境数据进行采集、还原、存储电路和DSP最小系统的设计,然后基于PMV及热舒适方程完成最适温度计算设定,并进行仿真论证,编写单片机程序,实现整个家电的智能化以及环境监测过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值