嵌入式电子烟防干烧算法:基于电阻实时监测的MCU保护程序设计与实现

1. 项目概述:为什么我们需要关注“防干烧”?

如果你自己动手做过电子烟,或者拆解过市面上的成品设备,大概率会碰到一个让人头疼又危险的问题:雾化芯烧糊了。那股焦糊味不仅毁了烟油的口感,更关键的是,高温下棉芯碳化产生的有害物质,对健康的影响远超正常雾化。这个问题的根源,就是“干烧”——当雾化芯里的导油棉未能及时补充烟油,而发热丝仍在持续加热时,就会发生。我最初DIY时,因为一个简单的延时点火逻辑没处理好,一晚上烧了三个手工绕制的克莱普顿线圈,那感觉真是又心疼又后怕。

“电子烟防干烧程序”这个项目,本质上就是为解决这个痛点而生的一个嵌入式软件方案。它不是一个独立的硬件,而是一套运行在电子烟主控芯片(通常是低成本、低功耗的MCU)上的智能控制算法。其核心使命是 实时监测雾化状态,在即将发生或已经发生干烧时,及时切断或限制加热功率,保护雾化芯,并提升使用的安全性与口感一致性 。听起来简单,但里面涉及到传感器选型、信号处理、控制算法和用户体验的精细平衡,远不是一句“没油就断电”那么简单。

这个项目适合几类人:一是电子烟DIY爱好者,想给自己的作品增加一层智能保护;二是嵌入式开发的新手,想找一个贴近生活、有明确输入输出的小项目练手;三是相关行业的硬件产品经理或工程师,需要深入理解用户体验与硬件可靠性之间的设计权衡。接下来,我会把这套程序的“五脏六腑”拆开,从设计思路到代码实操,再到踩过的坑,毫无保留地分享给你。

2. 整体设计思路与方案选型

设计防干烧程序,首先得搞清楚“如何判断干烧”。你不能等棉芯真的冒烟了再行动,那时已经晚了。我们需要一个或几个能够提前预警的“哨兵”。

2.1 核心监测原理的抉择

市面上和DIY圈里,常见的监测方案主要有三种,各有优劣:

  1. 电阻实时监测法(最主流、成本最低) :这是利用大多数电子烟都具备的“电阻读取”功能。发热丝(线圈)的电阻会随温度变化——温度越高,电阻越大(对于镍铬、不锈钢等常见材质)。当雾化芯处于饱和状态时,烟油能有效冷却线圈,其工作温度稳定,电阻在一个小范围内波动。一旦导油不畅,线圈冷却不足,温度会急剧上升,电阻也随之快速增加。通过高频采样线圈的实时电阻,并计算其变化率(dR/dt),就可以推断出温升速度,从而判断是否发生干烧。 优点 是无需额外硬件,利用现有电路即可实现。 缺点 是对MCU的ADC采样速度和算法有一定要求,且需要针对不同线圈材质(温控曲线不同)进行校准。

  2. 温度传感器直测法(最直接、最准确) :在雾化芯附近直接放置一个微型温度传感器,如NTC热敏电阻或数字温度传感器(如DS18B20)。直接读取温度值,与预设的安全阈值比较。 优点 是数据直观、准确,不受线圈材质影响。 缺点 是增加硬件成本和结构复杂度,传感器需要与发热体良好热耦合,且可能影响雾化仓内部气流和结构。

  3. 气流感知识别法(辅助判断) :通过麦克风或特定的压力传感器识别用户抽吸的气流声音或压力变化。结合抽吸信号,可以更准确地判断是正常使用还是静置干烧。例如,检测到有抽吸气流但电阻异常飙升,则干烧风险极高。 优点 是能结合用户行为,减少误判。 缺点 是系统更复杂,且容易受到环境噪音干扰。

对于个人DIY和小批量项目, 电阻实时监测法 无疑是平衡了可行性、成本和可靠性的最佳选择。我们的项目也将以此为核心展开。这意味着,你的电子烟主板必须能够测量发热丝两端的电压和回路电流,从而计算出实时电阻。

2.2 系统架构与工作流程

基于电阻法的防干烧程序,其核心是一个运行在MCU上的状态机。整个系统的工作流程可以概括为“采样-计算-判断-执行”的循环:

  1. 信号采样 :在发热丝工作的脉冲间隔(PWM调制的低电平期间)或专用采样周期,通过MCU的ADC通道,快速采集发热丝两端的电压和流过发热丝的电流(通常通过采样电阻获得)。
  2. 电阻计算 :根据欧姆定律 R = V / I,计算当前线圈的瞬时电阻值。为了提高精度,通常会对连续多个采样点进行滤波处理(如滑动平均滤波)。
  3. 状态判断 :这是算法的核心。程序会维护一个“基线电阻”值(通常是冷态电阻或近期稳定工作时的平均电阻)。通过比较实时电阻与基线电阻的差值,以及计算电阻在短时间内的上升斜率,来判断状态。
    • 正常状态 :电阻在基线附近小幅波动,上升斜率平缓。
    • 预警状态 :电阻持续上升,且斜率超过第一个阈值。系统可能采取降低功率或提示用户的措施。
    • 干烧状态 :电阻上升斜率超过第二个更高的阈值,或绝对阻值超过安全上限。系统必须立即切断加热。
  4. 控制执行 :根据判断出的状态,输出控制信号。正常状态维持原有功率;预警状态可能将PWM占空比减半;干烧状态则直接关闭MOS管,停止加热,并通过LED闪烁或屏幕提示用户。

这个循环的执行频率至关重要。太慢会错过干烧的早期信号,太快则会加重MCU负担。通常,一个10-100ms的循环周期是一个合理的起点。

3. 核心算法拆解与实操要点

确定了电阻法作为核心,接下来我们深入算法细节。这里面的每一个参数都不是随便填的,背后都有物理意义和实操考量。

3.1 基线电阻的动态建立与更新

你不能用一个固定的冷态电阻作为基线,因为线圈在工作一段时间后,整体温度会高于室温,其“热态”基线电阻是高于冷态的。一个健壮的算法需要使用动态基线。

实操方法 :在每次点火开始后的前1-2秒内,系统认为处于“稳定建立期”。将这期间计算出的平均电阻值,作为本次点火周期的初始基线电阻 R_base 。在后续持续加热过程中,如果系统判断处于“正常雾化”状态(即电阻变化平稳),则可以用一个很慢的遗忘因子(例如 R_base = 0.99 * R_base + 0.01 * R_current )来缓慢更新基线,使其能适应长时间连续抽吸带来的缓慢温升。

注意 :基线更新的速度必须非常慢,要确保它跟踪的是因整体热积累导致的缓慢变化,而不是因干烧导致的快速飙升。误更新基线会导致系统对干烧不敏感。

3.2 干烧判据的设计:双阈值法

单纯看电阻绝对值会误判,因为不同线圈、不同功率下的工作电阻本就不同。因此,我们主要依赖两个动态判据:

  1. 相对变化率阈值 :计算当前电阻 R_current 相对于当前基线 R_base 的增长百分比。 Delta_R = (R_current - R_base) / R_base * 100% 。例如,当 Delta_R > 15% 时,进入预警状态;当 Delta_R > 30% 时,判定为干烧。这个阈值需要根据线圈材质和常见烟油测试得出。

  2. 瞬时斜率阈值 :这是更灵敏的判据。计算过去N个采样周期内电阻的平均变化斜率。 Slope = (R_current - R_history[n]) / (N * T_sample) 。假设采样周期T_sample为50ms,N=4,即计算200ms内的电阻平均上升速度。如果斜率超过某个值(例如 0.1 Ohm/s ),即使绝对变化率还没到,也提前进入预警或干烧状态。这个判据对于快速干烧(如棉芯完全干涸)特别有效。

在代码中,这两个判据通常是“或”的关系 ,只要触发任何一个,就执行相应的保护动作。这构成了双保险。

3.3 信号采样与滤波的实战细节

在嘈杂的PWM控制环境中采集微弱的电压电流信号,是第一个技术挑战。

  • 采样时机 绝对不能在MOS管导通、大电流通过线圈时采样 ,此时采样电阻上的压降和线圈上的压降都包含巨大的开关噪声。必须在PWM周期的“关断期”进行采样。确保你的MCU定时器与ADC触发同步配置正确。
  • 硬件滤波 :在ADC输入引脚前,增加一个简单的RC低通滤波电路(如1kΩ电阻和0.1uF电容),可以极大抑制高频开关噪声。
  • 软件滤波 :单次ADC采样值不可信。通常需要连续采样5-10次,去掉最大最小值后求平均,再将这个平均值用于电阻计算。在电阻计算后,还可以对连续的电阻值进行滑动平均滤波,以平滑毛刺。
// 伪代码示例:获取滤波后的ADC值
#define SAMPLE_TIMES 8
uint32_t get_filtered_adc_value(ADC_ChannelTypeDef ch) {
    uint32_t samples[SAMPLE_TIMES];
    uint32_t sum = 0, min = 0xFFFFFFFF, max = 0;
    // 1. 采集一组样本
    for(int i=0; i<SAMPLE_TIMES; i++) {
        samples[i] = ADC_Read(ch);
        sum += samples[i];
        if(samples[i] < min) min = samples[i];
        if(samples[i] > max) max = samples[i];
    }
    // 2. 去掉最大最小值后求平均
    sum = sum - min - max;
    return sum / (SAMPLE_TIMES - 2);
}

4. 程序实现与关键代码解析

我们以一个基于STM32G0系列MCU(这类芯片在入门级电子烟主控中很常见)的简化项目为例,解析关键模块的实现。假设使用PWM驱动MOS管控制加热,并通过一个运放电路读取采样电阻的压降来获得电流。

4.1 外设初始化配置

首先,必须正确配置定时器(TIM)和模数转换器(ADC)。

// 关键初始化步骤概述
void Hardware_Init(void) {
    // 1. 初始化一个高频定时器(如TIM1)用于产生PWM
    // 频率通常为10-50Hz,占空比可变,控制加热功率
    PWM_Init(TIM1, 200, 0); // 假设200Hz,初始占空比0%

    // 2. 初始化ADC,用于采样线圈电压和电流
    // 需要两个ADC通道:一个用于线圈电压(分压后),一个用于采样电阻电压
    ADC_Init(ADC1, CH_VOLTAGE, CH_CURRENT);

    // 3. 配置一个基础定时器(如TIM6)产生中断,作为防干烧算法的主循环时钟
    // 中断周期设置为50ms
    BaseTimer_Init(TIM6, 50);
}

4.2 防干烧算法主循环(中断服务程序)

防干烧判断放在一个定时中断里执行,确保周期稳定。

// 全局变量定义
float R_base = 0.0f;      // 动态基线电阻
float R_current = 0.0f;   // 当前计算电阻
uint8_t dry_burn_status = STATUS_NORMAL; // 状态标志
#define ALARM_THRESHOLD_PERCENT 15.0f
#define BURN_THRESHOLD_PERCENT 30.0f
#define SLOPE_THRESHOLD 0.1f // Ohm/s

// TIM6 50ms中断服务程序
void TIM6_IRQHandler(void) {
    if(TIM6->SR & TIM_SR_UIF) { // 更新中断
        TIM6->SR &= ~TIM_SR_UIF; // 清除标志

        // 1. 只有在加热状态下才执行防干烧检测
        if(is_heating_active()) {
            // 2. 获取滤波后的电压、电流ADC值,并转换为实际电压/电流
            float V_coil = read_coil_voltage(); // 单位:伏特
            float I_coil = read_coil_current(); // 单位:安培

            // 3. 计算当前电阻 (避免除零)
            if(I_coil > 0.01f) {
                R_current = V_coil / I_coil;
            } else {
                R_current = R_base; // 电流过小,保持原值
            }

            // 4. 首次点火,建立基线
            static uint8_t init_counter = 0;
            if(init_counter < 20) { // 20*50ms = 1秒内建立基线
                static float sum_r = 0.0f;
                sum_r += R_current;
                init_counter++;
                if(init_counter == 20) {
                    R_base = sum_r / 20.0f;
                }
                return; // 基线建立期不进行保护判断
            }

            // 5. 计算相对变化率和斜率(简化示例,未展示历史队列)
            float delta_percent = (R_current - R_base) / R_base * 100.0f;
            // 假设已实现一个函数计算最近斜率
            float instant_slope = calculate_resistance_slope(); 

            // 6. 状态判断与执行
            dry_burn_status = STATUS_NORMAL; // 默认状态

            if(delta_percent > BURN_THRESHOLD_PERCENT || instant_slope > SLOPE_THRESHOLD * 2) {
                dry_burn_status = STATUS_DRY_BURN;
                // 立即关闭PWM输出,停止加热
                PWM_SetDuty(TIM1, 0);
                // 触发用户提示,如LED快闪3次
                user_alert(ALERT_BURN);
            } else if (delta_percent > ALARM_THRESHOLD_PERCENT || instant_slope > SLOPE_THRESHOLD) {
                dry_burn_status = STATUS_ALARM;
                // 预警状态,将功率降低至50%
                PWM_SetDuty(TIM1, get_target_duty() * 0.5f);
                // 轻微提示,如LED慢闪
                user_alert(ALERT_WARNING);
            } else {
                // 正常状态,恢复目标功率(如果之前被预警降低了)
                if(dry_burn_status == STATUS_ALARM) {
                    PWM_SetDuty(TIM1, get_target_duty());
                }
                // 非常缓慢地更新基线(仅当状态正常时)
                R_base = 0.999f * R_base + 0.001f * R_current;
            }
        } else {
            // 非加热状态,重置基线建立计数器
            init_counter = 0;
        }
    }
}

4.3 用户交互与状态提示

良好的用户体验要求程序不能“默默”地工作。当发生预警或干烧保护时,必须给用户清晰的反馈。

  • LED提示 :这是最常见的方式。可以定义不同的闪烁模式。
    • 正常 :常亮或呼吸灯。
    • 预警 :慢速闪烁(如亮1秒,灭1秒),提示用户烟油可能不足,或抽吸过频。
    • 干烧保护 :快速闪烁(如亮0.2秒,灭0.2秒,重复3-5次),然后停止加热,提示需要检查或添加烟油。
  • 屏幕显示 :对于有OLED屏的设备,可以直接显示“Dry Burn Protect”、“Check Liquid”等文字,或显示一个感叹号图标。
  • 触觉反馈 :有些高端设备带有微型振动马达,可以通过短震提示预警。

提示后,程序需要提供一个明确的“复位”路径。例如,在干烧保护触发后,设备应锁定点火功能,直到用户连续快速按多次点火键(如5次)确认,才解除保护状态。这可以防止在问题未解决时反复尝试点火。

5. 调试、校准与常见问题排查

程序写完了,烧录进板子,但事情远未结束。调试和校准才是让算法真正可靠的关键。

5.1 如何校准与确定阈值

你没有现成的“干烧传感器”来标定你的算法,所以校准是一个基于观察和测试的迭代过程。

  1. 搭建测试环境 :准备一个雾化器,使用你常用的线圈和棉芯。准备一个透明玻璃仓,方便观察烟油液面。
  2. 数据可视化(强烈推荐) :将MCU的电阻计算值 R_current delta_percent instant_slope 通过串口实时打印出来,并用电脑上的串口绘图工具(如Serial Plotter, CoolTerm)绘制成曲线图。这是最直观的调试方式。
  3. 正常雾化测试 :将烟油加满,以正常功率和抽吸节奏使用。观察电阻曲线的波动范围。记录下稳定雾化时 delta_percent 的最大值和 slope 的典型值。这将是你的安全区间。
  4. 人为制造干烧 :让雾化芯处于低油量状态,或者故意不润棉,然后点火。观察电阻曲线如何飙升。 重点观察曲线开始明显偏离正常基线的“拐点”
  5. 设定阈值 :将预警阈值设定在“拐点”之前的一个安全位置。例如,正常波动最大为5%,拐点出现在20%左右,那么可以将预警阈值设为10-12%,干烧阈值设为20-25%。斜率阈值同理,通过观察干烧时的上升速度来确定。
  6. 反复验证 :在不同功率、不同线圈、不同烟油粘度下重复测试,确保阈值具有普适性,不会在正常使用时误触发,也不会在真正干烧时失效。

5.2 常见问题与解决方案实录

以下是我在开发和测试过程中遇到的一些典型问题及解决方法,希望能帮你少走弯路。

问题现象 可能原因 排查步骤与解决方案
频繁误触发保护 (明明有油,却提示干烧) 1. 基线电阻 R_base 设置不当或更新过快。
2. 电阻采样噪声过大,导致计算值剧烈跳动。
3. 预警/干烧阈值设置过于敏感。
1. 检查基线 :通过串口打印 R_base R_current 。确保 R_base 在正常工作时相对稳定,且 R_current 围绕其波动。如果 R_base 跟踪过快,调慢更新系数(如从0.001改为0.0001)。
2. 优化采样 :检查硬件RC滤波参数,增加软件采样次数和滤波强度。确保在PWM关断期采样。
3. 调整阈值 :在正常雾化测试中,记录 delta_percent slope 的最大值,将预警阈值提高到最大值的1.5倍左右。
保护不触发 (棉芯已经烧糊,但程序没反应) 1. 算法循环执行周期太慢,错过了快速干烧信号。
2. 阈值设置过高。
3. 电流/电压采样计算有误,导致电阻值不准。
1. 加快循环 :将主循环定时器中断周期从50ms缩短到20ms或10ms。
2. 降低阈值 :在人为干烧测试中,观察触发保护时的实际 delta_percent ,适当调低阈值。
3. 校准采样 :用万用表实际测量线圈冷态电阻,与程序读取的初始电阻对比。校准ADC的参考电压和分压/放大比例系数。确保 V = ADC_V * scale_V , I = ADC_I * scale_I 中的 scale 系数准确。
保护动作后无法恢复 (触发一次干烧保护后,再也点不着火了) 状态机逻辑有缺陷,触发保护后没有正确的复位机制。 检查状态标志位。确保在保护触发后,除了关闭PWM,还要设置一个“保护锁定”标志。只有通过特定的用户操作(如快速按5次点火键)才能清除该标志,并将状态机重置为 STATUS_NORMAL
不同线圈表现差异大 不同材质(镍铬、不锈钢、钛丝)的电阻温度系数不同,导致同样的温升,电阻变化率不同。 1. 分材质预设 :在设备设置菜单中增加“线圈材质”选项,为不同材质加载不同的阈值参数组。
2. 自适应学习(进阶) :在每次更换线圈后的首次成功点火过程中,记录电阻从冷态到稳定工作态的变化曲线,自动推算一个合适的保护阈值。

5.3 进阶优化方向

当基础功能稳定后,可以考虑以下优化来提升体验:

  • 与温控模式协同 :如果你的设备支持温控(TC)模式,防干烧算法可以与之深度整合。在温控模式下,算法可以更宽松,因为温控本身就有温度上限;在功率(瓦特)模式下,则需要更严格的保护。
  • 学习用户习惯 :记录用户每次抽吸的时长和间隔。如果检测到用户频繁、长时间连续抽吸,可以主动在后续抽吸中轻微降低功率上限,进行预防性保护。
  • 基于 puff 计数的维护提醒 :结合防干烧触发的频率和抽吸口数,智能提醒用户“可能该更换雾化芯了”。例如,如果单位口数内触发预警的次数显著增加,提示“线圈性能下降”。

开发这样一个防干烧程序,最大的收获不是代码本身,而是对“可靠性设计”的深刻理解。在消费电子领域,尤其是与用户健康和安全相关的产品上,任何一个保护功能都不能是摆设。它必须基于真实的物理模型,经过充分的边界测试,并能优雅地处理各种异常情况。从最初简单的阈值判断,到后来引入动态基线、斜率监测、状态机,每一次迭代都源于一次实际的“烧芯”教训。现在,当我使用自己改装了这套程序的设备时,那种安心感是成品设备无法完全给予的。如果你也在做类似的项目,我强烈建议你花时间做好数据可视化和系统化测试,这比写代码本身更重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值