中科蓝讯SDK开发-BT897X、BT891X、AB573X、AB572X 系列 NTC过温保护功能教程
前言现在为止也开发了许多杰理中科蓝讯TWS蓝牙耳机、音响项目 SDK的案子在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习本章详细讲解中科蓝讯SDK开发-BT897X、BT891X、AB573X、AB572X 系列 NTC过温保护功能教程1、NTC硬件电路原理1.1 什么是 NTCNTCNegative Temperature Coefficient热敏电阻是一种电阻值随温度升高而降低的半导体器件。其核心参数参数本工程值含义R2510KΩ25°C 时的标称阻值B 值3380材料常数描述 R-T 曲线斜率封装贴片/环氧根据产品形态选择1.2 串联分压电路的基本原理先抛开热敏电阻看最基础的串联分压电路两个电阻首尾相接串在电源和地之间从中间节点引出输出电压。V_in电源电压 │ ┌───┴───┐ │ R1 │ └───┬───┘ │ ├──────── V_out输出电压即我们采样的点 │ ┌───┴───┐ │ R2 │ └───┬───┘ │ GND串联电路的三个基本事实电流相同R1 和 R2 串联在同一条回路上流过它们的电流 I 完全相同欧姆定律每个电阻两端的电压 U I × R电压相加两个电阻上的电压加起来等于电源电压U_R1 U_R2 V_in分压公式三步推导步骤求什么公式①回路总电阻R_total R1 R2②回路电流I V_in / (R1 R2)③R2 上的电压即输出V_out I × R2 V_in × R2 / (R1 R2)一条直觉规律串联电路中电压按阻值比例分配——谁的阻值大谁分到的电压就多R2 R1 时各分一半V_out V_in / 2R2 远大于 R1 时R2 几乎分走大部分电压R2 远小于 R1 时R2 只能分到小部分电压数字例子V_in 3.7VR1 100KR2 阻值V_out 3.7 × R2/(100KR2)说明368.7K2.91VR2 大分走大部分电压100K1.85VR2 R1恰好一半22.4K0.68VR2 小只分到零头1.3 从分压到测温把下臂换成热敏电阻现在把上面分压电路里的固定电阻 R2换成 NTC 热敏电阻记作 R_ntcR1 就是上拉电阻 R_pu中间节点接 ADC 引脚——这就是 NTC 测温电路VDD │ ┌───┴───┐ │ R_pu │ ← 上臂固定上拉电阻本工程 100K └──────┘ │ ├──────── V_adc → 接 ADC 引脚PB0 │ ┌───┴───┐ │ R_ntc │ ← 下臂热敏电阻阻值随温度变化 └──────┘ │ GND代入 1.2 节的分压公式V_adc VDD × R_ntc / (R_pu R_ntc)关键变化R_ntc 不再是固定值而是随温度变化NTC 特性温度越高阻值越小。于是温度一变下臂分到的电压就跟着变温度变化R_ntc下臂分到的电压V_adcADC 读数温度升高变小变少降低变小温度降低变大变多升高变大用本工程的数值感受VDD3.7VR_pu100K温度R_ntcV_adc含义0°C368.7K2.91V冷 → 阻值大 → 电压高25°C100K1.85V常温 → 恰好一半60°C22.4K0.68V热 → 阻值小 → 电压低测温链条就此成立温度 → R_ntcR-T 特性 → V_adc分压公式 → ADC 读数量化反过来用测出 ADC 读数 → 反推 R_ntc → 查 R-T 表得温度。这就是“用 saradc 采样热敏电阻分压值推算温度”的全部原理。最后一步量化ADC 把 V_adc 换算成 0~255 的数字ADC V_adc / VDD × 255 R_ntc / (R_pu R_ntc) × 255注意分子 V_adc 和参考 VDD 都正比于电源电压相除后 VDD 被约掉——所以 ADC 读数只取决于电阻比值与电池电量无关比率式测量详见 1.4 节。1.4 SARADC 采样精度AB573x 的 SARADC 硬件分辨率为10-bit0~1023软件提供两套读取接口接口精度范围定义api_saradc.h L25-26saradc_get_value10(ch)10-bit0~1023saradc_cb.value[ch]原始值saradc_get_value8(ch)8-bit0~255(u8)(saradc_cb.value[ch] 2)右移 2 位NTC 功能使用的是 8-bit 接口system.c L63if (saradc_get_value8(ADCCH_NTC) xcfg_cb.ntc_thd_val)因此阈值公式中的满量程是255而非 1023。ntc_thd_val在 xcfg.h 中定义为u8类型范围 0~255与 8-bit 接口匹配。其他信息特性值采样通道ADCCH_PB0channel 0config.h L697 定义参考电压VDD电源电压转换方式ping-pong每 5ms 读取上次结果并启动下次转换由于 ADC 参考电压与上拉电阻供电都是 VDD比率式测量ADC 读数仅取决于电阻分压比值与电池电压高低无关。电池从 4.2V 放电到 3.0V同一温度下 ADC 读数不变阈值无需随电压修正。扩展若需要更高精度的温度检测可自行修改sys_ntc_check()使用saradc_get_value10()接口此时公式中的 255 换成 1023阈值范围变为 0~1023。但需注意xcfg_cb.ntc_thd_val是 u8最大只能存 255若要用 10-bit 需额外扩展配置字段或用编译期宏代替。1.5 为什么选 100K 上拉 100K NTC阻抗匹配原理分压电路的输出灵敏度NTC 每变化 1ΩADC 变化多少为dV/dR_ntc VDD × R_pu / (R_pu R_ntc)²对 R_pu 求极值可知当 R_pu R_ntc 时灵敏度最大此时分压点恰好在 VDD/2ADC 中点 128。选择 100K 上拉 100K NTC 的三个理由理由说明① 阻抗匹配25°C 时 R_ntc100KR_puADC 读中点 128常温区分辨率最高② 低功耗回路电流 I VDD/(100K100K) ≈ 3.7V/200K ≈18μA对电池友好③ 自热小电流小 → NTC 自身发热小 → 测温更准1.6 若用 10K 上拉配 100K NTC 会怎样失配分析如果把上拉换成 10K内部或外部而 NTC 仍是 100K则 R_pu ≠ R_ntc严重失配。对比两种方案的 ADC 读数NTC 均为 100K B4250温度R_ntc (KΩ)方案A100K上拉方案B10K上拉0°C368.720124810°C212.817424420°C127.514323625°C100.0128中点232饱和区30°C79.011322640°C50.58621350°C33.26419660°C22.44717670°C15.434155结论方案B10K 上拉在常温区0~30°CADC 被压在 226~248 的饱和区20°C 的变化只换来 22 个 ADC 计数而方案A100K 上拉同样区间用了 113~201 共 88 个计数分辨率约为方案B的 4 倍。失配时分压点被 R_ntc “拉死”在接近 VDD 的位置温度变化几乎不改变 ADC 读数等效温度误差每 1 个 ADC 计数代表多少°C显著变大。所以上拉电阻必须与 NTC 在目标温度附近的阻值同数量级。本工程 NTC 是 100K上拉就必须用 100K不能用 10K。2. 阈值计算方法外部 100K 上拉方案2.1 公式ntc_thd_val R_ntc / (R_pu R_ntc) × 255本工程 R_pu 100K 100000Ω代入后ntc_thd_val R_ntc / (100000 R_ntc) × 255符号含义来源ntc_thd_val填入 代码中的ADC 的阈值0~255计算结果R_ntc目标保护温度下 NTC 的阻值Ω查 NTC datasheet 的 R-T 表100K B4250R_pu上拉电阻 100000Ω原理图 R4255ADC 采样精度8-bit 满量程固定值触发逻辑当 ADC 采样值≤ ntc_thd_val时温度已达或超过保护温度执行关机保护。2.2 计算步骤假设要在60°C触发保护步骤操作结果① 确定目标温度保护温度点60°C② 查 R-T 表100K B4250 在 60°C 的阻值R_ntc ≈ 22.2KΩ 22200Ω③ 代入公式22200 / (100000 22200) × 255 22200 / 122200 × 255 46.32569④ 取整填入四舍五入ntc_thd_val 473. 软件配置层3.1 编译期开关config.h L693-697/***************************************************************************** * Module : NTC预警关机功能配置 ******************************************************************************/ #define USER_NTC 0 // 总开关0关闭1编译NTC代码 #define ADCCH_NTC ADCCH_PB0 // NTC使用的ADC通道PB0 channel 0USER_NTC编译期总开关。设为 0 时所有 NTC 代码都不会被编译#if USER_NTC包裹。ADCCH_NTC选择 NTC 连接的 GPIO 对应的 SARADC 通道。可选ADCCH_PB0~PB7/ADCCH_PE4~PE7见config_define.hL62-70。3.2 出厂配置earphone.setting xcfg.xm即使USER_NTC1编译通过还有运行期开关Downloader 工具配置工具名称字段类型默认值说明是否使能NTCxcfg_cb.ntc_enboolFalse运行期总开关NTC使用内部10K上拉xcfg_cb.ntc_user_inner_puboolFalse使能内部上拉NTC阈值xcfg_cb.ntc_thd_valu81008-bit ADC 阈值≤此值触发对应xcfg.xm定义L26-28config(CHECK, 是否使能NTC, 是否使能NTC, NTC_EN, 0); config(CHECK, NTC使用内部10K上拉, ...使能程序中的宏USER_NTC后才有效, NTC_USER_INNER_PU, 0, NTC_EN); config(BYTE, NTC阈值, NTC阈值, NTC_THD_VAL, 0, 255, 100, NTC_EN);对应xcfg.h位域L27-29u32 ntc_en : 1; //是否使能NTC u32 ntc_user_inner_pu : 1; //NTC使用内部10K上拉 u8 ntc_thd_val; //NTC阈值0~2553.3 配置联动关系双重开关┌─────────────────────────────────────────────────────────────────┐ │ USER_NTC 1 ? │ │ ↓ YES ↓ NO │ │ 代码被编译 所有NTC代码不存在 │ │ ↓ │ │ xcfg_cb.ntc_en True ? │ │ ↓ YES ↓ False │ │ 运行期检测生效 sys_ntc_check()立即return 0 │ │ ↓ │ │ xcfg_cb.ntc_user_inner_pu ? │ │ ↓ True ↓ False │ │ adcch_io_pu10k_enable() 使用外部上拉硬件设计 │ │ ↓ │ │ xcfg_cb.ntc_thd_val ? │ │ 与ADC采样值比较的阈值 │ └─────────────────────────────────────────────────────────────────┘要启用 NTC 保护必须同时满足config.h中USER_NTC 1编译期Downloader 工具中勾选是否使能NTCntc_en True正确配置阈值ntc_thd_val硬件上 NTC 接到对应 ADC 引脚4. 软件运行框架4.1 完整数据流图┌─────────────── 初始化阶段 ───────────────────────────────────────┐ │ │ │ sys_init() → bsp_saradc_init() → saradc_init() saradc_start() │ │ → adkey_init() │ │ ├─ if(ntc_user_inner_pu) adcch_io_pu10k_enable() │ │ └─ if(ntc_en) adc_ch | BIT(ADCCH_NTC) │ │ → saradc_set_channel(adc_ch) //使能NTC ADC通道 │ │ → sys_set_tmr_enable(1, 1) //启动5ms/1ms定时器 │ │ │ └───────────────────────────────────────────────────────────────────┘ ↓ 每5ms循环 ┌─────────────── 采样阶段SARADC 硬件 ──────────────────────────┐ │ │ │ SARADC 自动转换ping-pong 模式 │ │ 每轮 bsp_saradc_process() │ │ ① saradc_is_finish() → 检查上轮转换完成 │ │ ② saradc_get_result() → 读取结果到 saradc_cb.value[] │ │ ③ saradc_start() → 启动下轮转换 │ │ 结果saradc_cb.value[ADCCH_NTC] 10-bit ADC值 │ │ │ └───────────────────────────────────────────────────────────────────┘ ↓ 同一次5ms中断 ┌─────────────── 检测阶段sys_ntc_check ────────────────────────┐ │ │ │ usr_tmr5ms_thread() [system.c L99-195, 5ms中断上下文] │ │ ├─ bsp_key_scan() // L116: 含 bsp_saradc_process() │ │ │ └─ 读取ADC结果到 saradc_cb.value[] │ │ └─ sys_ntc_check() // L131-133: NTC检测 │ │ ├─ if(!ntc_en) return 0 // 未使能直接跳过 │ │ ├─ adc_val saradc_get_value8(ADCCH_NTC) // 取8-bit值 │ │ ├─ if(adc_val ntc_thd_val) // ★超温判定 │ │ │ ├─ n_cnt // 去抖计数 │ │ │ └─ if(n_cnt 20) // 连续20次100ms │ │ │ → sys_cb.pwroff_flag.ntc_ind 1 // ★置标志 │ │ │ → return 1 │ │ └─ else: n_cnt 0 // 恢复正常清零计数 │ │ │ └───────────────────────────────────────────────────────────────────┘ ↓ 标志位被设置后 ┌─────────────── 执行阶段pwroff_process ───────────────────────┐ │ │ │ func_process() [func.c L534-651, MAIN线程每轮消息泵调用] │ │ └─ pwroff_process() [func.c L234-270] │ │ ├─ if(pwroff-all_flag ! 0) // ★检查所有关机标志 │ │ │ ├─ charge_full_ind → tone_en0 (无声关机) │ │ │ ├─ aux_insert_ind → tone_en2 (AUX提示音) │ │ │ └─ else(含ntc_ind) → tone_en1 (播放关机提示音) │ │ └─ func_cb.sta FUNC_PWROFF // ★切换到关机态 │ │ │ │ func_run() while(1) → case FUNC_PWROFF: │ │ └─ func_pwroff(tone_en1) [func_lowpwr.c L16-107] │ │ ├─ 播放 RES_IDX_POWEROFF 关机提示音 │ │ ├─ 断开蓝牙 func_bt_chk_off() │ │ ├─ 等待按键松开 │ │ ├─ dac_power_off() bsp_saradc_exit() │ │ └─ sfunc_pwrdown(1) → lowpwr_pwroff_mode() → 关机 │ │ │ └───────────────────────────────────────────────────────────────────┘4.2 sys_ntc_check() 源码详解代码讲解// system.c L54-76 #if USER_NTC AT(.com_text.ntc) u8 sys_ntc_check(void) { if (!xcfg_cb.ntc_en) { // ① 运行期开关检查Downloader配置 return 0; } static int n_cnt 0; // ② 去抖计数器static保持跨调用 if (xcfg_cb.ntc_en) { if (saradc_get_value8(ADCCH_NTC) xcfg_cb.ntc_thd_val) { // ③ 核心判定 if (n_cnt 20) { // ④ 连续20次×5ms 100ms 去抖 sys_cb.pwroff_flag.ntc_ind 1; // ⑤ 置关机标志 return 1; } else { n_cnt; // ⑥ 计数递增 } } else { n_cnt 0; // ⑦ ADC恢复温度回落清零重来 } } return 0; } #endif去抖设计解读100ms 去抖20×5ms足够滤除 ADC 噪声/瞬间干扰归零式去抖中间只要有一次不满足就清零重来非累计式确保只有持续超温才触发一旦ntc_ind1函数每次都返回 1因为n_cnt 20持续为真但标志已置位不影响4.3 关机标志联合体// system.h L27-44 struct pwroff_tag { volatile union { struct { u8 charge_full_ind : 1; // bit0: 充满关机 u8 ntc_ind : 1; // bit1: ★NTC过温关机 u8 low_bat_ind : 1; // bit2: 低电关机 u8 aux_insert_ind : 1; // bit3: AUX插入关机 u8 pwr_key_ind : 1; // bit4: 按键关机 u8 timeout_ind : 1; // bit5: 超时关机 u8 user_ind : 1; // bit6: APP/TWS等用户操作 }; u8 all_flag; // 任意一位置1 → all_flag ! 0 }; u8 tone_en; // 关机提示音类型 ... };pwroff_process()func.c L252通过all_flag ! 0一次性检测所有关机源。NTC 触发后tone_en 1播放正常关机提示音RES_IDX_POWEROFF进入FUNC_PWROFF→func_pwroff()→ 断蓝牙 → 关 DAC →sfunc_pwrdown()→ 芯片进入 Power Down 模式4.4 调用时序图时间轴 ──────────────────────────────────────────────────────────────→ 5ms中断: │────│────│────│────│────│────│────│... ↓ ↓ ↓ ↓ ↓ ↓ ↓ NTC检测: [正常][正常][超温][超温][超温]...[超温×20][触发!] n0 n0 n1 n2 n3 n20 ntc_ind1 ↓ MAIN线程消息泵: func_process() ↓ pwroff_process() ↓ all_flag≠0 → FUNC_PWROFF ↓ func_pwroff(tone_en1) ↓ 播放关机音 → 断BT → powerdown5. 充电模式下 NTC 保护缺失分析5.1 问题本质公版 SDK 设计NTC 保护仅在非充电模式耳机放电态生效。充电模式下无 NTC 保护。根因分析两条路径都不工作条件非充电模式充电模式 (CHARGE_LOW_POWER_EN0)充电模式 (CHARGE_LOW_POWER_EN1)5ms 中断✓ 正常触发✓ 仍在触发⚠ Timer0 重配为 RC2M 1ms调度改变sys_ntc_check()✓ 被调用✓ 仍被调用⚠ 可能被调用但 SARADC 时钟切 RCntc_ind 被置位✓✓ 可以置位⚠ 不确定pwroff_process()✓ 被 func_process 调用✗ 不被调用✗ 不被调用关机动作执行✓✗ 永远不执行✗ 永远不执行关键断点func_charge_process()func_charge.c L134-175有自己独立的while(1)循环void func_charge_process(void) { u32 counter 0; while (1) { WDT_CLR(); delay_5ms(1); // 等待5ms中断仍触发 u8 charge_sta sfunc_charge_process(counter); // 仅充电处理 if(charge_sta CHAG_STA_OFF) { // 只检查DC拔出 func_cb.sta func_cb.last; break; } // ← 这里从不检查 pwroff_flag // ← 从不调用 func_process() / pwroff_process() } }即使 5ms 中断里sys_ntc_check()把ntc_ind置了 1充电循环永远不读取该标志所以保护动作永远不执行。5.2 如何为充电模式添加 NTC 保护在sfunc_charge_process()或func_charge_process()的循环中添加 NTC 检测调用方案 A在 sfunc_charge_process() 中添加推荐// func_charge.c → sfunc_charge_process() 内添加 u8 sfunc_charge_process(u32 counter) { ... if ((counter % 20) 0) { // 每100ms检测一次counter每次15ms charge_process(); #if USER_NTC if (sys_ntc_check()) { // ★添加NTC检测 return CHAG_STA_OFF; // 超温 → 退出充电或自定义处理 } #endif } ... }方案 B在 func_charge_process() while循环中直接检查标志while (1) { WDT_CLR(); delay_5ms(1); #if USER_NTC if (sys_cb.pwroff_flag.ntc_ind) { // ★检查NTC标志5ms ISR中已置位 // 充电中NTC超温处理 // 方案1停止充电 关机 sfunc_charge_exit(); sys_cb.pwroff.tone_en 0; // 充电中不播提示音喇叭可能未使能 func_cb.sta FUNC_PWROFF; break; // 方案2仅停止充电不关机 // charge_off(); // sys_cb.pwroff_flag.ntc_ind 0; // 清标志 // break; // 回到上个功能态 } #endif u8 charge_sta sfunc_charge_process(counter); ... }方案选择建议充电时超温通常是电池安全问题建议停止充电 关机方案 B 方案 1若产品需要停止充电但不关机如仅降低充电电流可自定义处理逻辑注意充电模式下 DAC/提示音可能已被关闭dac_power_off()infunc_charge_enter所以tone_en0静默关机更合理5.3 CHARGE_LOW_POWER_EN1 时的额外注意若启用了充电低功耗模式CHARGE_LOW_POWER_EN1系统时钟切换到 RC2M2MHzTimer0 重配为 1ms ISR而非原来的 5msSARADC 时钟切换到 RCfunc_charge_process()使用delay_us(350)而非delay_5ms(1)此时usr_tmr5ms_thread()的调用频率可能改变不再是精确 5msSARADC 采样精度可能受 RC 时钟影响若要在充电低功耗模式下做 NTC需验证 ADC 采样准确性或在func_charge_process的 tight loop 中直接调用bsp_saradc_process()sys_ntc_check()制作不易喜欢的小伙伴给个小赞赞喜欢我的小伙伴点个关注有不懂的地方和需要的资源随时问我哟