ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

中科蓝讯SDK开发-BT897X、BT891X、AB573X、AB572X 系列 NTC过温保护功能教程

中科蓝讯SDK开发-BT897X、BT891X、AB573X、AB572X 系列 NTC过温保护功能教程

前言

现在为止也开发了许多杰理,中科蓝讯TWS蓝牙耳机、音响项目 SDK的案子,在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总,方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习;

本章详细讲解中科蓝讯SDK开发-BT897X、BT891X、AB573X、AB572X 系列 NTC过温保护功能教程

1、NTC硬件电路原理

1.1 什么是 NTC

NTC(Negative Temperature Coefficient)热敏电阻是一种电阻值随温度升高而降低的半导体器件。其核心参数:

参数本工程值含义
R2510KΩ25°C 时的标称阻值
B 值3380材料常数(描述 R-T 曲线斜率)
封装贴片/环氧根据产品形态选择

1.2 串联分压电路的基本原理

先抛开热敏电阻,看最基础的串联分压电路:两个电阻首尾相接串在电源和地之间,从中间节点引出输出电压。

V_in(电源电压) │ ┌───┴───┐ │ R1 │ └───┬───┘ │ ├──────── V_out(输出电压,即我们采样的点) │ ┌───┴───┐ │ R2 │ └───┬───┘ │ GND

串联电路的三个基本事实:

  1. 电流相同:R1 和 R2 串联在同一条回路上,流过它们的电流 I 完全相同
  2. 欧姆定律:每个电阻两端的电压 U = I × R
  3. 电压相加:两个电阻上的电压加起来等于电源电压,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 / 2
  • R2 远大于 R1 时:R2 几乎分走大部分电压
  • R2 远小于 R1 时:R2 只能分到小部分电压

数字例子(V_in = 3.7V,R1 = 100K):

R2 阻值V_out = 3.7 × R2/(100K+R2)说明
368.7K2.91VR2 大,分走大部分电压
100K1.85VR2 = R1,恰好一半
22.4K0.68VR2 小,只分到零头

1.3 从分压到测温:把下臂换成热敏电阻

现在把上面分压电路里的固定电阻 R2换成 NTC 热敏电阻(记作 R_ntc),R1 就是上拉电阻 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 读数
温度升高变小变少降低变小
温度降低变大变多升高变大

用本工程的数值感受(VDD=3.7V,R_pu=100K):

温度R_ntcV_adc含义
0°C368.7K2.91V冷 → 阻值大 → 电压高
25°C100K1.85V常温 → 恰好一半
60°C22.4K0.68V热 → 阻值小 → 电压低

测温链条就此成立:

温度 → R_ntc(R-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-bit(0~1023),软件提供两套读取接口:

接口精度范围定义(api_saradc.h L25-26)
saradc_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 L63):

if (saradc_get_value8(ADCCH_NTC) <= xcfg_cb.ntc_thd_val)

因此阈值公式中的满量程是255(而非 1023)。ntc_thd_val在 xcfg.h 中定义为u8类型,范围 0~255,与 8-bit 接口匹配。

其他信息:

特性值
采样通道ADCCH_PB0(channel 0,config.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/2(ADC 中点 128)。

选择 100K 上拉 + 100K NTC 的三个理由:

理由说明
① 阻抗匹配25°C 时 R_ntc=100K=R_pu,ADC 读中点 128,常温区分辨率最高
② 低功耗回路电流 I = VDD/(100K+100K) ≈ 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Ω)方案A:100K上拉方案B:10K上拉
0°C368.7201248
10°C212.8174244
20°C127.5143236
25°C100.0128(中点)232(饱和区)
30°C79.0113226
40°C50.586213
50°C33.264196
60°C22.447176
70°C15.434155

结论:方案B(10K 上拉)在常温区(0~30°C)ADC 被压在 226~248 的饱和区,20°C 的变化只换来 22 个 ADC 计数;而方案A(100K 上拉)同样区间用了 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 B4250)
R_pu上拉电阻 =100000Ω原理图 R4
255ADC 采样精度(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 = 47

3. 软件配置层

3.1 编译期开关(config.h L693-697)

/***************************************************************************** * Module : NTC预警关机功能配置 ******************************************************************************/ #define USER_NTC 0 // 总开关:0=关闭,1=编译NTC代码 #define ADCCH_NTC ADCCH_PB0 // NTC使用的ADC通道(PB0 = channel 0)
  • USER_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_NTC=1编译通过,还有运行期开关(Downloader 工具配置):

工具名称字段类型默认值说明
是否使能NTCxcfg_cb.ntc_enboolFalse运行期总开关
NTC使用内部10K上拉xcfg_cb.ntc_user_inner_puboolFalse使能内部上拉
NTC阈值xcfg_cb.ntc_thd_valu81008-bit ADC 阈值(≤此值触发)

对应xcfg.xm定义(L26-28):

config(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-29):

u32 ntc_en : 1; //是否使能NTC u32 ntc_user_inner_pu : 1; //NTC使用内部10K上拉 u8 ntc_thd_val; //NTC阈值(0~255)

3.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 保护,必须同时满足:

  1. config.h中USER_NTC = 1(编译期)
  2. Downloader 工具中勾选"是否使能NTC"(ntc_en = True)
  3. 正确配置阈值(ntc_thd_val)
  4. 硬件上 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_en=0 (无声关机) │ │ │ ├─ aux_insert_ind → tone_en=2 (AUX提示音) │ │ │ └─ else(含ntc_ind) → tone_en=1 (播放关机提示音) │ │ └─ func_cb.sta = FUNC_PWROFF // ★切换到关机态 │ │ │ │ func_run() while(1) → case FUNC_PWROFF: │ │ └─ func_pwroff(tone_en=1) [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_ind=1,函数每次都返回 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][触发!] n=0 n=0 n=1 n=2 n=3 n=20 ntc_ind=1 ↓ MAIN线程消息泵: func_process() ↓ pwroff_process() ↓ all_flag≠0 → FUNC_PWROFF ↓ func_pwroff(tone_en=1) ↓ 播放关机音 → 断BT → powerdown

5. 充电模式下 NTC 保护缺失分析

5.1 问题本质

公版 SDK 设计:NTC 保护仅在非充电模式(耳机放电态)生效。充电模式下无 NTC 保护。

根因分析(两条路径都不工作):

条件非充电模式充电模式 (CHARGE_LOW_POWER_EN=0)充电模式 (CHARGE_LOW_POWER_EN=1)
5ms 中断✓ 正常触发✓ 仍在触发⚠ Timer0 重配为 RC2M 1ms,调度改变
sys_ntc_check()✓ 被调用✓ 仍被调用⚠ 可能被调用但 SARADC 时钟切 RC
ntc_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每次+1=5ms) 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_en=0静默关机更合理

5.3 CHARGE_LOW_POWER_EN=1 时的额外注意

若启用了充电低功耗模式(CHARGE_LOW_POWER_EN=1):

  • 系统时钟切换到 RC2M(2MHz)
  • Timer0 重配为 1ms ISR(而非原来的 5ms)
  • SARADC 时钟切换到 RC
  • func_charge_process()使用delay_us(350)而非delay_5ms(1)

此时:

  • usr_tmr5ms_thread()的调用频率可能改变(不再是精确 5ms)
  • SARADC 采样精度可能受 RC 时钟影响
  • 若要在充电低功耗模式下做 NTC,需验证 ADC 采样准确性,或在func_charge_process的 tight loop 中直接调用bsp_saradc_process()+sys_ntc_check()

制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!

返回列表