最近作者也是在维护一段工程中出现大量的我看不懂的代码,这个代码中出现大量的枚举(enum)。联想起曾几何时观看的博客,状态机的知识点正在慢慢的在我脑海中挥之不去。事已至此为何不快马加鞭立马做上笔记,以防忘记.......
 

        本人郑重承诺:所有文章均不设置任何观看门槛均免费

首先我们看一段代码

1️⃣有状态机

  • 🙈 NTC 温度保护状态机

// 当前代码(有状态机)
switch(ntc_level)
{
    case NTC_LOW_TEMP:      // 0~15°C - 小电流充电
    case NTC_HIGH_TEMP:     // 15~45°C - 正常充电
    case NTC_OUTRANGE_TEMP: // <-20°C 或 >60°C - 关机保护
    case NTC_DIS_CHARGE_TEMP: // <0°C 或 >45°C - 禁充临界
}
  • 🙉 霍尔开关盖状态机

// 当前代码(有状态机)
switch(u16Open50TimerCount)
{
    case TIME_50MS:      // 开盖消抖
    case TIME_3S:        // 3 秒通信
    case TIME_2S5:       // 2.5 秒保护检查
    case TIME_5S:        // 5 秒超时
}

switch(u16Close50TimerCount)
{
    case TIME_3S:   // 关盖 3 秒
    case TIME_10S:  // 关盖 10 秒复位耳机
    case TIME_14S:  // 关盖 14 秒发送命令
    case TIME_15S:  // 关盖 15 秒进入睡眠
}
  • 🙊 充电状态机

// 当前代码(有状态机)
typedef enum 
{
    NO_CHARGING = 0x00,
    CHARGING = 0x01,
    CHARGE_FULL = 0x02
} charge_state_t;

charge_state_t l_charge_state, r_charge_state;

2️⃣没状态机

  • 🐶NTC 温度保护(没有状态机)

// ❌ 灾难现场:无尽的 if-else 嵌套
if(ntc_value >= 3708)  // <-17°C
{
    if(ntc_value >= 3850)  // <-30°C
    {
        // 超低温关机
        Earbuds_Shutdown_Process();
        ICP1108_Charge_Enable(0);
        ICP1108_CHx_Func(CHN_OFF);
        
        // 下次检测又要重复判断...
        if(ntc_value < 3708)
        {
            // 又要在每个地方判断是否恢复
            if(ntc_value <= 2439 && ntc_value >= 1344)
            {
                GW_Earbuds_Resume_Charge();
            }
        }
    }
    else
    {
        // 低温充电
        ICP1108_Set_Charge_Current(ICP1108_ICC_MIN);
        ICP1108_Charge_Enable(1);
        
        // 又要判断是否超温...
        if(ntc_value < 3708)
        {
            // 又要判断具体温度区间...
            if(ntc_value <= 2536)
            {
                // 切换到高温逻辑...
            }
        }
    }
}
else if(ntc_value <= 2536)  // 15°C
{
    // 又一个温度区间...
    // 代码开始重复、分散、难以维护
}
else if(ntc_value <= 1344)  // 41°C
{
    // 第三个温度区间...
}
else
{
    // 高温处理...
}

 😓问题:
 1. 每个需要温度判断的地方都要复制一遍逻辑
 2. 状态转换不清晰,容易遗漏边界条件
 3. 添加新温度点需要修改所有 if-else
 4. 无法统一管理状态转换历史

  • 🐺 霍尔开关盖(没有状态机)

// ❌ 灾难现场:定时器 + 标志符混乱
unsigned char bOpenLid50ms_Done = 0;
unsigned char bOpenLid3s_Done = 0;
unsigned char bOpenLid2p5s_Done = 0;
unsigned char bCloseLid10s_Done = 0;
unsigned char bCloseLid14s_Done = 0;
// ... 20+ 个标志符

// 在每个循环中:
if(Hall_Is_Open())
{
    if(timer_open >= 50 && !bOpenLid50ms_Done)
    {
        // 执行 50ms 操作
        bOpenLid50ms_Done = 1;
        
        // 但是其他地方也要检查这个标志...
        if(bOpenLid50ms_Done && !bOpenLid3s_Done)
        {
            if(timer_open >= 3000)
            {
                // 执行 3 秒操作
                bOpenLid3s_Done = 1;
                
                // 逻辑开始失控...
                if(bOpenLid3s_Done && timer_open >= 2500)
                {
                    // 2.5 秒检查...
                    // 但要考虑如果 3 秒时已经做过怎么办?
                    // 如果用户中途关盖了怎么办?
                    // 标志符越来越多...
                }
            }
        }
    }
}
else
{
    // 关盖逻辑又要一套标志符...
    // 还要处理开盖和关盖标志符的互斥
    // 清理时机难以把握
}

🙄 问题:
 1. 标志符爆炸式增长(20+ 个)
 2. 标志符之间的依赖关系复杂
 3. 清理标志符的时机容易出错
 4. 无法直观看出状态转换流程

  •  🐱充电状态(没有状态机)

// ❌ 灾难现场:多个变量表示同一状态
unsigned char l_is_charging = 0;
unsigned char l_charge_done = 0;
unsigned char l_plugged_in = 0;
unsigned char r_is_charging = 0;
unsigned char r_charge_done = 0;
unsigned char r_plugged_in = 0;

// 判断逻辑分散在各处:
if(l_is_charging && !l_charge_done && l_plugged_in)
{
    // 正在充电...
}

if(!l_is_charging && l_charge_done)
{
    // 充满了但没在充...
    // 但这个状态合理吗?需要检查 l_plugged_in...
    if(!l_plugged_in)
    {
        // 哦,原来是被取出了
    }
}

 问题:
 1. 多个变量组合表示状态,容易不一致
 2. 可能出现非法组合(如 is_charging=1 且 charge_done=1)
 3. 每次判断都要写一大串条件
 4. 新增状态(如故障)需要添加更多变量

        是不是感觉代码量突然间变多了?
        但是你会发现这个两者之间仅仅只差了一个"switch_case"那我用"if-else"不也是一样的吗?

        首先你看"if-else"的写法是不是要加大量的逻辑判断语句。这样的好处就是能让你的整个工程看起来像一坨大便。这样不仅仅会消耗计算周期而且还会增大代码的阅读难度。造成难以维护.这一个思想就直接"从条件判断转变成传值了"。

  • 3️⃣举个例子🌰--现在有一个充电盒子的工程

👩‍🎓原工程

// 之前:每次都要问"温度是多少度?"
if(温度 < 0℃) 
{ 
    小电流充电; 
}
else if(温度 < 15℃) 
{ 
    中电流充电; 
}
else
{ 
    大电流充电; 
}

这是简单的例子现在来个复杂的例子


if(温度超限)
{
    stop_charge();
}
else if(电池电压 < 3V)
{
    set_small_current();
    start_charge();
}
else if(温度 < 0)
{
    set_small_current();
    start_charge();
}
else if(温度 < 15)
{
    set_medium_current();
    start_charge();
}
else
{
    set_large_current();
    start_charge();
}

👨‍🎓第一次加条件:增加一个“电池电量过低”条件

        这段代码看起来还行,但问题在于:如果以后增加一个“电池电量过低”条件,或者增加一个“充电时间过长”条件,你要在哪里插进去?继续往 if-else 链里加?那这个函数会越来越长,越来越难维护。现在就实际的给你演示一遍《一坨大便是怎么诞生》的。这个时候你那伟大的产品经理性质冲冲的说“张工,加个“电池电量低于 10% 时,也要用小电流充电,保护电池。”
        请问你要插那里去???

        按理说应该在“电池电压 < 3V”之后,“温度 < 0”之前,因为电量过低比温度优先级高?还是比温度低?产品经理没说清楚。

if(温度超限)
{
    stop_charge();
}
else if(电池电压 < 3V)
{
    set_small_current();
    start_charge();
}
else if(电池电量 < 10%)   // 新加的条件
{
    set_small_current();
    start_charge();       // 和上面重复了
}
else if(温度 < 0)
{
    set_small_current();
    start_charge();       // 又重复了
}
else if(温度 < 15)
{
    set_medium_current();
    start_charge();
}
else
{
    set_large_current();
    start_charge();
}

        现在有 6 个分支了。你看,set_small_current() 和 start_charge() 这段代码出现了三次。如果你以后要改小电流的值,要在三个地方改。


🧑‍🎓第二次加条件:增加“充电时间过长”

        测试说:“连续充电超过 10 小时,要停止充电,防止过充。”这个条件放哪里?应该在所有充电条件之前?还是在某些条件下才能触发?

if(充电时间 > 10小时)   // 新加的条件,放最前面
{
    stop_charge();
}
else if(温度超限)
{
    stop_charge();
}
else if(电池电压 < 3V)
{
    set_small_current();
    start_charge();
}
else if(电池电量 < 10%)
{
    set_small_current();
    start_charge();
}
else if(温度 < 0)
{
    set_small_current();
    start_charge();
}
else if(温度 < 15)
{
    set_medium_current();
    start_charge();
}
else
{
    set_large_current();
    start_charge();
}

        现在 7 个分支了。而且出现了两个 stop_charge() 分支,两个 set_small_current() 分支。重复越来越多。


🧑‍🎓第三次加条件:增加“充电次数限制”

商务说:“电池充电超过 500 次后,要限制充电电流,延长寿命。”

加在哪里?优先级比电压高还是低?比温度高还是低?

if(充电时间 > 10小时)
{
    stop_charge();
}
else if(温度超限)
{
    stop_charge();
}
else if(充电次数 > 500)   // 新加的条件
{
    set_small_current();   // 又一个小电流
    start_charge();
}
else if(电池电压 < 3V)
{
    set_small_current();   // 又一个小电流
    start_charge();
}
else if(电池电量 < 10%)
{
    set_small_current();   // 又一个小电流
    start_charge();
}
else if(温度 < 0)
{
    set_small_current();   // 又一个小电流
    start_charge();
}
else if(温度 < 15)
{
    set_medium_current();
    start_charge();
}
else
{
    set_large_current();
    start_charge();
}

        现在 8 个分支了。set_small_current() 和 start_charge() 这对代码重复了 5 次。


        🧑‍🏫第四次加条件:增加“快充模式”

             硬件说:“新增一个快充开关,打开时可以用更大的电流充电。” 加在哪里?快充模式和温度、电压、电量之间是什么关系?如果温度高但开了快充,怎么办?如果电压低但开了快充,怎么办?

if(充电时间 > 10小时)
{
    stop_charge();
}
else if(温度超限)
{
    stop_charge();
}
else if(快充模式 && 温度 < 30)   // 新加的复杂条件
{
    set_extra_large_current();   // 新的大电流
    start_charge();
}
else if(充电次数 > 500)
{
    set_small_current();
    start_charge();
}
else if(电池电压 < 3V)
{
    set_small_current();
    start_charge();
}
else if(电池电量 < 10%)
{
    set_small_current();
    start_charge();
}
else if(温度 < 0)
{
    set_small_current();
    start_charge();
}
else if(温度 < 15)
{
    set_medium_current();
    start_charge();
}
else
{
    set_large_current();
    start_charge();
}

现在 9 个分支了。你开始发现问题了:

  1. 优先级越来越乱:新条件放哪里?放前面会被旧条件挡住,放后面又可能优先级不对。你每次都要想半天。

  2. 重复代码越来越多set_small_current() 和 start_charge() 重复了 5 次。改一次要改 5 个地方。

  3. 逻辑分散:小电流充电的条件有 5 个,散落在各个地方。你想知道“什么情况下会小电流充电”,要把这 9 个分支全读一遍。

  4. 条件组合复杂:快充模式要和温度条件组合,但这个组合只出现在这一个分支里。如果以后要加“快充模式+电压低”怎么办?再开一个分支?


👩‍🏫第五次加条件:增加“涓流充电”阶段

        充电芯片说:“电池电压极低时,要先涓流充电,再小电流充电。”加在哪里?要区分电池电压 < 2.5V2.5V ~ 3V 两种情况。

if(充电时间 > 10小时)
{
    stop_charge();
}
else if(温度超限)
{
    stop_charge();
}
else if(快充模式 && 温度 < 30)
{
    set_extra_large_current();
    start_charge();
}
else if(充电次数 > 500)
{
    set_small_current();
    start_charge();
}
else if(电池电压 < 2.5V)   // 新加的涓流条件
{
    set_trickle_current();   // 新函数
    start_charge();
}
else if(电池电压 < 3V)
{
    set_small_current();
    start_charge();
}
else if(电池电量 < 10%)
{
    set_small_current();
    start_charge();
}
else if(温度 < 0)
{
    set_small_current();
    start_charge();
}
else if(温度 < 15)
{
    set_medium_current();
    start_charge();
}
else
{
    set_large_current();
    start_charge();
}

现在 10 个分支了。这坨大便已经成型了:

  • 10 个 else-if 分支,顺序就是优先级

  • 任何新条件都要考虑往哪里插,插错一个整个逻辑就乱

  • 同样的动作重复出现,修改时要小心翼翼

  • 没有人敢轻易动这段代码,因为它已经成了一座“屎山”


🐮这就是 if-else 的宿命

        每次加一个新条件,你都要:

  1. 找个地方插进去

  2. 考虑和其他条件的优先级

  3. 复制粘贴已有的动作代码

  4. 祈祷不要破坏现有逻辑

你的工程里有多少个这样的条件?

  • 温度区间:7 个

  • 电压区间:4 个

  • 电量区间:3 个

  • 充电时间限制

  • 充电次数限制

  • 快充模式开关

  • 涓流充电阶段

  • 回滞逻辑(温度从低到高和从高到低不一样)

  • 耳机插入状态

  • USB 插入状态

  • 霍尔开关状态

        如果你把所有条件都用 if-else 写在一个函数里,分支数会是 7×4×3×2×2×2×2×2×2……你自己算算吧。这已经不是一坨大便了,这是一座核废料山,谁碰谁死。


4️⃣对比:状态机是怎么做的?

用状态机,你不按“条件”来组织代码,而是按“状态”来组织:

enum 
{
    STATE_STOP,           // 停止充电
    STATE_TRICKLE,        // 涓流充电
    STATE_SMALL,          // 小电流充电
    STATE_MEDIUM,         // 中电流充电
    STATE_LARGE,          // 大电流充电
    STATE_EXTRA_LARGE     // 快充模式
} charge_state;

// 第一步:根据所有条件计算当前应该处于什么状态
charge_state = calculate_charge_state(温度, 电池电压, 电池电量, 充电时间, 
{
    // 第一优先级:安全相关的强制停止条件
    if(充电时间 > 10小时)
        return STATE_STOP;
    if(温度超限)  // 温度太高或太低
        return STATE_STOP;
    // 第二优先级:电池保护相关
    if(电池电压 < 2.5V)
        return STATE_TRICKLE;   // 涓流充电
    if(电池电压 < 3V)
        return STATE_SMALL;     // 小电流
    if(电池电量 < 10%)
        return STATE_SMALL;     // 小电流
    if(充电次数 > 500)
        return STATE_SMALL;     // 小电流
    // 第三优先级:温度区间
    if(温度 < 0)
        return STATE_SMALL;     // 小电流
    if(温度 < 15)
        return STATE_MEDIUM;    // 中电流
    // 第四优先级:快充模式
    if(fast_charge_mode && 温度 < 30)
        return STATE_EXTRA_LARGE;  // 超大电流
    // 默认状态
    return STATE_LARGE;  // 大电流
}                                     充电次数, 快充模式);

// 第二步:根据状态执行动作
switch(charge_state)
{
    case STATE_STOP: 
        //这个是函数 calculate_charge_state()返回STATE_STOP的选项       
        stop_charge(); 
        break;
    case STATE_TRICKLE:
        //这个是函数 calculate_charge_state()返回STATE_TRICKLE的选项          
        set_trickle(); 
        start_charge(); 
        break;
    case STATE_SMALL:
        //这个是函数 calculate_charge_state()返回STATE_SMALL的选项     
        set_small(); 
        start_charge(); 
        break;
    case STATE_MEDIUM:  
        //这个是函数 calculate_charge_state()返回STATE_MEDIUM的选项   
        set_medium(); 
        start_charge(); 
        break;
    case STATE_LARGE:
        //这个是函数 calculate_charge_state()返回STATE_LARGE的选项       
        set_large(); 
        start_charge(); 
        break;
    case STATE_EXTRA_LARGE: 
        //这个是函数 calculate_charge_state()返回STATE_EXTRA_LARGE的选项
        set_extra_large(); 
        start_charge(); 
        break;
}

5️⃣从内存角度来理解状态机核心的本质

        C语言一大特性就是 C语言 + 直接操作设备内存;能够直接操作设备内存,那么现在就回到内存的本质。还是以上面充电和工程为例

        首先,企业代码的特点是某个特定的模块封装了一定的功能,相同的模块放在同一文件下。这里不谈各个模块的调用机制。我们就从变量入手。

        来更深层次的聊解状态机的本质

🐮先了解if - else的机制(由浅入深)

首先,明确无论是if - else还是switch - case他们的本质都是改变一个变量

🐮区别:但是前者是通过逻辑判断。后者是传值

🦁联系:都是改变一个值进行一定的操作

---------------------------很好,那请问那种的效率更高?那哪钟更利于维护?------------------------🤗🤩🤔🫡🤨😐😑😶😶‍🌫️🙄😏😣😥😮🤐😯😪😫🥱😴🙄😏😣😥😮🤔🫡🤨

答案如下

🫡效率比较

        在大多数编程语言中,switch-case结构通常比if-else链更高效。原因在于switch-case可能被编译器优化为跳转表(jump table)实现,使得时间复杂度接近O(1),而if-else链需要按顺序逐个检查条件,时间复杂度为O(n)

🤗性能差异场景:

  • 分支数量较少时(如3个以内),两者性能差异可忽略
  • 分支数量较多时(如10个以上),switch-case的优势更明显
  • 当条件判断是等值比较(==)时,switch-case更高效
  • 当条件需要复杂逻辑判断时(如范围判断),if-else是唯一选择

🤔可维护性比较

switch-case通常在以下方面更利于维护:

  • 代码结构更清晰,所有分支平铺展示
  • 新增分支时只需添加一个case,不需要修改其他条件
  • 更适合处理枚举类型的值

if-else在以下场景维护性更好:

  • 条件判断需要复杂逻辑时(如组合条件)
  • 需要处理范围判断时(如x > 10 && x < 20)
  • 条件有优先级要求
// 之前:每次都要问"温度是多少度?"
if(温度 < 0℃) 
{ 
    小电流充电; 
}
else if(温度 < 15℃) 
{ 
   中电流充电; 
}
else大电流充电
{ 
    大电流充电; 
}

        但是最终是不是去访问内存中的一个值,if-else的关系如下

        因为,在C语言中,变量名是唯一的,不允许重复定义。因此,状态机机制可以在整个工程中使用同一个全局变量来表示当前状态。改变这个变量的值,本质上就是在修改它所在内存地址中存储的数据。

换句话说,当我们访问这个变量名时,实际上就是在间接访问内存中的那个值。

        从这个角度来看,无论是 if-else 还是 switch-case,它们的核心本质都是一样的——都是通过改变变量的值来改变内存中的状态

        只不过,它们改变这个值的方式不同,对代码结构的影响程度也不同。接下来,我们就来深入分析一下这两种方式在修改状态值时的具体方法和工程意义。


拿快递的启示:if-else 与状态机“switch-case”的思维差异

        打个比方,这就好比你拿快递。

        if-else 的做法是这样的:你到了快递驿站,工作人员不告诉你快递在哪,而是让你一个个条件去匹配——

        👮🏻“你是不是张三?

        👨不是。

        👮🏻你是不是李四?

        👨不是。

        👮🏻你是不是王五?

        👨哦是!

        👮🏻那你的快递是手机吗?

        👨不是。

        👮🏻是衣服吗?

        👨不是。

        👮🏻是充电器吗?

        👨是!

好,找到了。”

        每拿一次快递,就要把这一套条件全部过一遍。如果快递驿站中途重新整理了货架,或者新增了一批快递,那这套条件可能就要全部推倒重来。更可怕的是,如果你在某个条件分支里加了新逻辑,你根本不知道会不会影响别的分支——因为所有逻辑都纠缠在一起,牵一发而动全身。

        这种做法的困境在于:当需求变化时,你要反复思考

“我在这里改,会不会影响别处?”

“新增一个判断条件,放在哪里最合适?放在前面还是中间?”

“万一放错了,整个取件流程会不会崩溃?”

       

        现在,换成状态机的思路——用 switch-case 来实现:

        你一到驿站,工作人员先根据你的身份,给你一个状态码(取件码——1、2、3、4、5)。这个状态码决定了你接下来走什么流程:

  • 状态码 1(VIP 客户):去 VIP 窗口,工作人员核对身份,直接从专属货架取件。

  • 状态码 2(普通客户):去普通窗口,扫码验证,从普通货架取件。

  • 状态码 3(企业客户):去企业通道,批量取件,走对公流程。

  • 状态码 4(异常件客户):去客服窗口,人工处理。

  • 状态码 5(退货客户):去退货专窗,走退件流程。

        这个 switch-case 结构,本质上就是一个状态机的实现方式——根据当前所处的“状态码”(也就是状态),决定执行哪一段逻辑,执行完后,再决定下一个状态码是什么。就相当于人家给你一个值。你根据值去找什么


        新增需求?状态机“switch-case”说:小事一桩

        现在回到我们前面提到的硬件场景。

        假设我在系统中新增了一个NTC(负温度系数热敏电阻,一种用电阻值变化来检测温度的传感器)模块。

        过段时间,客户说:“不行,我要用PTC(正温度系数热敏电阻,工作原理与NTC相反,但功能同样是测温)。”

        如果用 if-else 的方式,我可能要把所有涉及温度检测的地方翻一遍,找到所有 if (isNTC) 改成 if (isPTC),或者再加个 else if。改完还要反复测试,生怕漏掉某个地方,导致温度显示不准,甚至设备保护功能失效。

        但用状态机switch-case”呢? 我只需要在状态机的内部处理里,把“温度检测”这个状态对应的具体实现,从 NTC 驱动换成 PTC 驱动就行了。外部调用者根本不知道底层换了传感器——它们只知道“当前是温度检测状态”,然后状态机会自动调用正确的读取函数。

🦒先了解“switch-case”的机制

  • ❤️计算阶段

        通过函数(例如:calculate_charge_state)集中处理所有条件判断,返回枚举类型的数值结果:

return STATE_SMALL;     // 小电流
return STATE_TRICKLE;   // 中电流
return STATE_MEDIUM;    // 大电流

内存中就标定了这个模块此时的值

🩷存储阶段

将计算结果存入内存变量

calculate_charge_state(温度, 电池电压, 电池电量, 充电时间, 充电次数, 快充模式);

🧡执行阶段:通过读取内存变量值触发对应行为:

  • 枚举状态
/**
 * @brief 充电状态枚举
 */
typedef enum {
    STATE_SMALL  = 0,   /* 小电流充电 - 预充/激活模式 */
    STATE_MEDIUM = 1,   /* 中电流充电 - 恒流充电阶段 */
    STATE_LARGE  = 2,   /* 大电流充电 - 快充阶段 */
    STATE_CV     = 3,   /* 恒压充电阶段 - 电压恒定,电流递减 */
    STATE_FULL   = 4,   /* 充电完成 - 已充满 */
    STATE_ERROR  = 5    /* 异常状态 - 故障保护 */
} charge_state_t;
  • 决定那种状态
/**
 * @brief 计算当前充电状态
 * 
 * 根据电池的温度、电压、电量、充电时间、充电次数以及快充模式等参数,
 * 综合判断当前应该处于哪种充电状态。
 * 
 * @param temperature   电池温度(单位:0.1℃,例如 250 表示 25.0℃)
 * @param battery_voltage   电池电压(单位:mV)
 * @param battery_capacity  电池当前电量(单位:mAh 或百分比)
 * @param charge_time       本次充电已持续时长(单位:秒)
 * @param charge_cycles     电池累计充电次数(影响充电策略)
 * @param fast_charge_mode  快充模式使能标志(1:启用快充,0:普通充电)
 * 
 * @return 当前充电状态值
 *         - STATE_SMALL:  小电流充电(预充/恒流充电阶段)
 *         - STATE_MEDIUM: 中电流充电(恒流充电阶段)
 *         - STATE_LARGE:  大电流充电(快充阶段)
 *         - STATE_CV:     恒压充电阶段
 *         - STATE_FULL:   充电完成
 *         - STATE_ERROR:  异常状态(温度过高/过低、电池故障等)
 */
int current_state = calculate_charge_state(temperature, battery_voltage,                         
                                           battery_capacity,charge_time, 
                                            charge_cycles, 
                                            fast_charge_mode);
  • 状态分发

/**
 * @brief 根据当前充电状态执行对应的充电策略
 * 
 * 通过状态机的方式,根据计算得到的充电状态,执行相应的充电电流控制,
 * 确保电池在不同阶段都能安全、高效地充电。
 */
switch(current_state) 
{
    case STATE_SMALL: 
        /* 小电流充电 */
        /* 适用场景:电池电压过低(< 3.0V)或温度过低/过高时,
           使用较小电流(如 0.1C)激活电池或保护电池安全 */
        small_current_charge();
        break;
        
    case STATE_MEDIUM: 
        /* 中电流充电 */
        /* 适用场景:电池进入恒流充电阶段,使用标准充电电流(如 0.5C-1C),
           在效率和安全性之间取得平衡 */
        medium_current_charge();
        break;
        
    case STATE_LARGE: 
        /* 大电流充电(快充模式) */
        /* 适用场景:电池状态良好且启用快充模式时,
           使用较大电流(如 2C-3C)快速补充电量 */
        large_current_charge();
        break;
        
    case STATE_CV: 
        /* 恒压充电阶段 */
        /* 适用场景:电池电压达到恒压阈值(如 4.2V),
           电压保持不变,电流逐渐减小,直至充满 */
        constant_voltage_charge();
        break;
        
    case STATE_FULL: 
        /* 充电完成 */
        /* 适用场景:电池已充满,停止充电并进入待机状态,
           可开启涓流补充或直接关闭充电 */
        charge_complete();
        break;
        
    case STATE_ERROR: 
        /* 异常状态处理 */
        /* 适用场景:电池温度过高(> 60℃)、过低(< 0℃)、
           电池电压异常、充电超时等异常情况 */
        charge_error_handler();
        break;
        
    default:
        /* 未知状态处理 */
        /* 适用场景:理论上不会执行到此分支,作为安全兜底 */
        unknown_state_handler();
        break;
}
  • 图解

        这里的“calculate_charge_state()”函数时返回一个特定的状态,然后由switch-case”进行特定的跳转。充电管理系统的状态机采用计算与跳转分离的设计模式,通过calculate_charge_state()函数和switch-case结构实现逻辑判断与执行操作的解耦

  • 😶‍🌫️状态计算函数工作原理

calculate_charge_state()函数接收六类关键参数进行综合评估:

  1. 电池温度监测:检测过热或过冷状态
  2. 电池电压分析:判断低压预充需求或恒压点到达
  3. 电池电量评估:确定快充启动条件或充电完成临界点
  4. 充电时间监控:防止超时风险
  5. 充电次数统计:根据电池老化调整策略
  6. 快充模式状态:确认用户授权状态

        该函数通过内置的优先级算法和安全规则,输出唯一确定的状态值。其核心功能是进行状态判定,不涉及任何硬件操作。

  • 👊状态执行分发流程

        状态值通过switch-case结构进行路由分发:

  1. STATE_SMALL触发小电流充电流程,调用small_current_charge()设置100mA电流
  2. STATE_MEDIUM启动中电流充电,执行medium_current_charge()配置1A标准电流
  3. STATE_LARGE激活大电流快充,运行large_current_charge()设置2A电流
  4. STATE_CV进入恒压阶段,调用constant_voltage_charge()锁定4.2V电压
  5. STATE_FULL完成充电,执行charge_complete()关闭充电电路
  6. STATE_ERROR转入保护模式,运行charge_error_handler()中止充电
  • 😊架构优势分析——分离式设计带来显著效益:
  1. 逻辑隔离提升可维护性,状态判断与硬件控制完全解耦
  2. 修改灵活性增强,策略调整与硬件变更互不影响
  3. 测试便利性改善,支持独立验证判断逻辑和执行功能
  • 😴系统运行类比

        类似餐厅运营机制:

  1. 状态计算相当于顾客点餐决策过程
  2. 状态分发对应后厨的标准化烹饪流程
  3. 两个环节通过订单状态自然衔接,保持高效协作

💛状态机优势对比

特性 if - else 直接执行 switch - case 状态机
计算频率 每次判断都需重新计算 单次计算多次复用
逻辑集中度 分散在各处 集中于状态计算函数
可维护性 修改需调整多处 仅需修改状态计算逻辑
调试可见性 无中间状态记录 可通过变量打印实时状态

💙if-else 与 switch-case 性分析

  • 以下通过代码实例和底层原理说明两者的等价性:
// if-else 实现
if (x == 1) 
    action1();
else if (x == 2) 
    action2();
else if (x == 3) 
    action3();

// switch-case 实现
switch(x) 
{
    case 1: action1(); 
         break;
    case 2: action2(); 
        break;
    case 3: action3(); 
        break;
}
  • 在x86汇编层面,两种结构都会生成类似的比较-跳转指令序列。典型汇编模式如下:
; if-else 生成的汇编
cmp eax, 1
je L1
cmp eax, 2
je L2
cmp eax, 3
je L3

; switch-case 生成的汇编(少量分支时)
cmp eax, 1
je L1
cmp eax, 2
je L2
cmp eax, 3
je L3

💜状态机的本质特征

状态机的核心要素包括:

  • 有限的状态集合
  • 明确的转移条件
  • 状态隔离的行为逻辑

        从这个角度来看,if-else 和 switch-case 其实本质上是相通的——它们都是在根据变量的值进行分支跳转。所以,从这里可以反映出:状态机其实是一种编程思想,压根就不是什么特定的语法或语句

        它只是一种模块化编程的体现——把复杂的逻辑拆分成若干个独立的状态,每个状态只关心自己内部的事情,状态之间通过明确的规则进行转移。至于具体是用 if-else 实现,还是用 switch-case 实现,甚至是用函数指针数组实现,这些都只是实现方式,而不是状态机本身。

        真正重要的是那种“以状态为核心来组织代码”的思维方式。

🩵编译器优化差异

  • 当分支数量超过阈值(通常5-7个),编译器会对switch-case采用跳转表优化:
; 跳转表实现示例
jmp [jump_table + eax*4]
  • 这种优化使得时间复杂度从O(n)降至O(1),但if-else无法自动获得这种优化。
  • 实现形式可以多样化:
// 函数指针数组实现
void (*states[])(void) = {state_idle, state_active, state_error};
states[current_state]();

🤎选择建议

        少量分支(≤4个)时优先使用if-else,结构更直观。大量分支时switch-case具有性能优势,且代码可读性更好。关键是根据状态转移逻辑的复杂度选择最匹配的实现方式。

今天就分享到这里,拜拜我去改BUG啦

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐