跳转到内容
新建笔记

BQ27427:寄存器、可靠读数与配置流程

BQ27427 是单节锂电池电量计,主机通过 I²C 读取电压、温度、电流、剩余容量和 SOC,并按电池与系统参数配置其估算模型。它与充电器、电池保护、模拟增益控制分属不同职责;读出 75% 并不证明充电器正在工作,也不代表已完成参数配置。

本页给出经过编译和模拟总线测试的 C 读数层,以及配置流程的检查点。具体 STM32 引脚、I²C 外设、RTOS、超时和电池参数由板级适配层提供。串口协议和 Python 界面见电量与增益控制协议。

flowchart LR
B[电池与采样电路] --> G[BQ27427]
G <-->|I²C| D[单一电量计任务]
D --> S[带时间与有效状态的读数]
S --> P[串口协议任务]
P <-->|UART| H[上位机]
P --> A[增益硬件控制]

地址、寄存器与数值类型

跳转到“地址、寄存器与数值类型”

七位 I²C 地址是 0x55;在线路上,包含读写位的地址字节为写 0xAA、读 0xAB。本页适配函数接收七位地址。若底层 STM32 HAL 接口要求左移地址,应只在适配层转换一次,不能在调用链中重复左移。

以下为本应用相关命令。普通双字节读数按低字节在前组装;Data Memory 配置中的双字节参数则按其另行规定的高字节在前格式处理。

命令起始地址长度含义或单位
Control0x002子命令及其结果,例如 DEVICE_TYPE
Temperature0x022无符号,0.1 K
Voltage0x042无符号,mV
Flags0x062状态位
NominalAvailableCapacity0x082无符号,mAh,标称可用容量
FullAvailableCapacity0x0A2无符号,mAh,可用满容量
RemainingCapacity0x0C2无符号,mAh,当前条件下剩余容量
FullChargeCapacity0x0E2无符号,mAh,当前条件下满充容量
AverageCurrent0x102有符号,mA;正值为净充电、负值为净放电
AveragePower0x182有符号,mW
StateOfCharge0x1C2无符号百分比,正常范围 0~100
InternalTemperature0x1E2无符号,0.1 K,内部温度
StateOfHealth0x201百分比,按 Rev. B 第 5.13 节读取低字节
FullChargeCapacityFiltered0x2E2无符号,mAh,滤波后的满充容量

原寄存器表中的 StandbyCurrent(0x12)、MaxLoadCurrent(0x14) 不能作为 BQ27427 的标准命令使用;0x20 不是循环次数,0x2E 不是健康度。当前 TRM 总表遗漏了 SOH 项,但第 5.13 节明确给出 0x20 和单字节返回。应同时核对命令章节,不能从相近型号搬运地址。TI BQ27427 技术参考手册

温度的精确换算为:

TC=Traw10−273.15,TcentiC=10Traw−27315.T_{\mathrm{C}}=\frac{T_{\mathrm{raw}}}{10}-273.15, \qquad T_{\mathrm{centiC}}=10T_{\mathrm{raw}}-27315.

采用有符号 32 位百分之一摄氏度可保留换算精度,例如原始值 2731 对应 −0.05 ℃,不能简单减去整数 2731 并声称精确到 0.1 ℃。协议需要 0.1 ℃ 时,再由发送端约定舍入和溢出处理。

Flags 位名称应用解释
0DSG电量计放电状态;其模式说明中,放电及 RELAX 均可置位,不能取反后直接当成充电器连接状态
1 / 2SOCF / SOC1两级低电量警告,阈值由配置决定
3BAT_DET检测到电池;受 BIN/BIE 或主机电池插入命令的工作方式影响
4CFGUPMODE正处于配置更新模式
5ITPOR配置 RAM 已复位,需要按应用重新装入配置
8CHG允许快速充电;不是“当前正在充电”
9FC电量计满充判定
14 / 15UT / OT低温/过温状态;原文 OTD、OTC 命名不适用于此处

原始标志位和通信成功应分别保存。BAT_DET 未建立、配置丢失或尚处于配置模式时,读操作可以成功,但不能因此把 SOC 当成已正确初始化的有效估计。充电状态若面向用户,应按充电器状态、净电流阈值与系统工作模式共同定义;零电流还可能意味着静置。SOC 的 0%/100% 是算法和系统截止条件下的估计端点,不等于物理电荷绝对为零或必然已触发充电器满充。

同一个任务或受互斥保护的事务负责访问电量计,尤其要保护“写 Control 子命令—等待—读结果”的整个序列。仅给每个底层 I²C 调用加锁,会允许其他任务在间隙覆盖子命令。

当前手册要求包间总线空闲至少 66 µs;超过 100 kHz 时写入采用单字节写,读取仍可多字节。读写型命令结果保守等待至少 2 s,普通只读命令无需这种结果等待,但同一标准命令不应每秒访问超过两次。因此本例每轮结束后至少间隔 1 s 才允许下一轮,失败也限速。66 µs 来自手册图示,PDF 文本提取有时会把 µs 错读成 ms。

底层还需支持时钟延展,并设置有限超时;手册给出的延展上限随运行模式不同。不要把一次 100 ms 超时乘上多次访问后仍称为“5 ms 完成”。长等待在 RTOS 中应让出处理器;总线失败后的重探测也应由任务退避安排,不能在中断里循环阻塞。

将下列代码保存为 bq27427_read.c,或拆成头文件与实现文件。bq_bind 绑定同步适配函数,bq_probe 检查精确型号 0x0427;它们没有执行电池参数配置。bq_read_sample 先读局部变量,只有全部读数及范围检查成功才覆盖输出,失败时保留上次成功数据。调用者必须另存有效状态和时间,避免把旧值当成新读数。

/* BQ27427 readout example. Single owner; the caller owns bus serialization. */
#include <stdint.h>
#include <stddef.h>
#include <string.h>
enum {
BQ_OK = 0, BQ_PARAM = -1, BQ_IO = -2, BQ_WRONG_DEVICE = -3,
BQ_NOT_PROBED = -4, BQ_RANGE = -5, BQ_NOT_DUE = -6
};
enum {
BQ_DSG = 1u << 0, BQ_SOCF = 1u << 1, BQ_SOC1 = 1u << 2,
BQ_BAT_DET = 1u << 3, BQ_CFGUPMODE = 1u << 4, BQ_ITPOR = 1u << 5,
BQ_CHG_ALLOWED = 1u << 8, BQ_FC = 1u << 9,
BQ_UT = 1u << 14, BQ_OT = 1u << 15
};
typedef struct {
void *user;
/* Synchronous transfers; return 0 only after all requested bytes complete.
addr7 is 0x55, reg is an 8-bit command address. Use finite bus timeouts. */
int (*read)(void *, uint8_t addr7, uint8_t reg, uint8_t *, size_t);
int (*write_byte)(void *, uint8_t addr7, uint8_t reg, uint8_t);
/* Wait at least this duration; an RTOS port should yield for long waits. */
void (*wait_us)(void *, uint32_t);
uint32_t (*now_ms)(void *);
} BqBus;
typedef struct {
uint8_t soc_pct, soh_pct;
uint16_t voltage_mv, remaining_mah, full_mah, flags;
int16_t current_ma, power_mw;
int32_t temperature_centi_c;
uint32_t started_ms, completed_ms;
} BqSample;
typedef struct {
BqBus bus;
uint32_t last_attempt_end_ms;
uint8_t probed, attempted;
} BqGauge;
int bq_bind(BqGauge *g, const BqBus *bus)
{
if (!g || !bus || !bus->read || !bus->write_byte ||
!bus->wait_us || !bus->now_ms) return BQ_PARAM;
BqGauge fresh = {0};
fresh.bus = *bus;
*g = fresh;
return BQ_OK;
}
static int bq_read_bytes(BqGauge *g, uint8_t reg, uint8_t *p, size_t n)
{
int result = g->bus.read(g->bus.user, 0x55, reg, p, n);
g->bus.wait_us(g->bus.user, 66); /* Packet-to-packet bus-free interval. */
return result == 0 ? BQ_OK : BQ_IO;
}
static int bq_read_u16(BqGauge *g, uint8_t reg, uint16_t *value)
{
uint8_t p[2];
int result = bq_read_bytes(g, reg, p, sizeof p);
if (result == BQ_OK) *value = (uint16_t)((uint16_t)p[1] << 8 | p[0]);
return result;
}
static int16_t bq_signed_u16(uint16_t value)
{
/* Keep the cast in range, including on pre-C23 implementations. */
int32_t signed_value = (value & 0x8000u) ? (int32_t)value - 65536 : value;
return (int16_t)signed_value;
}
int bq_probe(BqGauge *g)
{
if (!g || !g->bus.read || !g->bus.write_byte ||
!g->bus.wait_us || !g->bus.now_ms) return BQ_PARAM;
g->probed = 0;
/* DEVICE_TYPE=0x0001. Separate one-byte writes also support >100 kHz.
No other owner may write Control() until this transaction finishes. */
const uint8_t bytes[2] = {0x01, 0x00};
for (uint8_t reg = 0; reg < 2; ++reg) {
int result = g->bus.write_byte(g->bus.user, 0x55, reg, bytes[reg]);
g->bus.wait_us(g->bus.user, 66);
if (result != 0) return BQ_IO;
}
/* Conservative read/write-command result wait from TRM 3.3. */
g->bus.wait_us(g->bus.user, 2000000);
uint16_t id;
if (bq_read_u16(g, 0x00, &id) != BQ_OK) return BQ_IO;
if (id != 0x0427) return BQ_WRONG_DEVICE;
g->probed = 1;
return BQ_OK;
}
static int bq_sample_attempt(BqGauge *g, BqSample *out)
{
BqSample sample = {0};
uint16_t soc, current, temperature, power;
sample.started_ms = g->bus.now_ms(g->bus.user);
if (bq_read_u16(g, 0x1C, &soc) != BQ_OK ||
bq_read_u16(g, 0x04, &sample.voltage_mv) != BQ_OK ||
bq_read_u16(g, 0x10, &current) != BQ_OK ||
bq_read_u16(g, 0x02, &temperature) != BQ_OK ||
bq_read_u16(g, 0x0C, &sample.remaining_mah) != BQ_OK ||
bq_read_u16(g, 0x0E, &sample.full_mah) != BQ_OK ||
bq_read_u16(g, 0x18, &power) != BQ_OK ||
bq_read_bytes(g, 0x20, &sample.soh_pct, 1) != BQ_OK ||
bq_read_u16(g, 0x06, &sample.flags) != BQ_OK) return BQ_IO;
if (soc > 100 || sample.soh_pct > 100) return BQ_RANGE;
sample.soc_pct = (uint8_t)soc;
sample.current_ma = bq_signed_u16(current);
sample.power_mw = bq_signed_u16(power);
sample.temperature_centi_c = (int32_t)temperature * 10 - 27315;
sample.completed_ms = g->bus.now_ms(g->bus.user);
*out = sample; /* Publish only after every read and validation succeeds. */
return BQ_OK;
}
int bq_read_sample(BqGauge *g, BqSample *out)
{
if (!g || !out) return BQ_PARAM;
if (!g->probed) return BQ_NOT_PROBED;
uint32_t now = g->bus.now_ms(g->bus.user);
if (g->attempted && (uint32_t)(now - g->last_attempt_end_ms) < 1000u)
return BQ_NOT_DUE;
int result = bq_sample_attempt(g, out);
/* Failed attempts are also rate-limited; the caller retains prior data. */
g->last_attempt_end_ms = g->bus.now_ms(g->bus.user);
g->attempted = 1;
return result;
}
void bq_forget_local_state(BqGauge *g)
{
if (g) g->probed = 0; /* No bus command: this does not reset the gauge. */
}

这里的九次寄存器读取不是芯片原子快照,started_ms 与 completed_ms 只描述这一轮主机访问区间。调用层也不能让其他任务同时读写同一个 BqSample;可以使用队列按值发布、受锁保护地复制,或采用经过论证的快照机制。32 位毫秒计数的差值运算允许一次正常回绕,但系统不能在长期停调度后把多次完整回绕当作可测时间。

本例不把读取 SOH 的成功误称为健康诊断,不捏造循环次数,也不提供只修改本地变量却名为芯片“软复位”的函数。bq_forget_local_state 仅让主机下次重新探测;真实 RESET(0x0041) 与 SOFT_RESET(0x0042) 的作用和访问条件不同,必须在独立配置/恢复流程中使用。

配置不是“连通即可”

跳转到“配置不是“连通即可””

量产配置至少要核对实际电池的化学模型、设计容量、设计能量及缩放、系统截止电压、充电终止条件、温度来源和电池插入检测方式。芯片提供有限的预设化学模型;不能只按标称电压猜测,也不能声称可以随意下载任意电芯模型。参数应来自电池资料、充电策略和实际验证。TI BQ27427 数据手册

下面是配置事务的顺序检查表;它没有替实际电池选择数值,也不是可以直接盲写到任何板卡的初始化脚本。

  1. 确认设备型号、密封状态及访问权限;需要时按设备既定解封流程进入 UNSEALED。暂停正常读数发布,并独占该器件的总线事务。
  2. 发送 SET_CFGUPDATE(0x0013),至少等待 1100 ms,然后以有限期限轮询 CFGUPMODE;未进入就退出并记录失败。
  3. 通过 BlockDataControl(0x61)、DataClass(0x3E) 和 DataBlock(0x3F) 选择所需配置块。选择后至少等待 5 ms,再读取 0x40~0x5F 的完整 32 字节及旧校验,先确认旧块自洽。
  4. 在内存副本中修改已确认的字段,并保留其他字节。例如 State 类 ID 为十进制 82(0x52),块 0 中 Design Capacity、Design Energy、Terminate Voltage、Taper Rate 的偏移分别为 6、8、10、21,均需按该字段规定处理高低字节与单位。Taper Rate 是比率参数,不能直接填充终止电流的 mA 数值。
  5. 按允许的写包方式写入更新字节,计算整个 32 字节块的校验:checksum = 255 - (sum(block) & 0xFF),写 0x60 才使块内容提交到配置 RAM。随后至少等待 5 ms,重新选择同块并等待,回读内容和校验逐项验证。
  6. 发送 SOFT_RESET(0x0042) 退出配置,等待并检查 CFGUPMODE、ITPOR 清除以及读数重新有效;按产品策略重新密封。失败不能简单跳到“初始化成功”。

这些参数保存在 RAM,电量计掉电后必须重新装入。CONFIG UPDATE 大约 240 s 后自动退出也不等于事务已经完整提交;主机应有更短的明确期限。上述 1100 ms 和数据块等待按当前手册第 5.1.9、8.5 节检查,旧示例中的约 1 s 不能覆盖新版明确下限。

板级移植依次完成总线适配、型号探测、配置与有效性管理、定期读数、串口发布,最后再连接界面与增益硬件。增益命令只在硬件执行成功后返回成功 ACK;未知电量、失败状态和周期计数来源要显式表达,不能用零值代替有效数据。

现象优先检查
无应答或型号不符供电、共地、上拉、电平、七位/左移地址约定、器件实际型号
电流符号或温度异常小端组装、有符号解码、温度单位、采样与温度源配置
SOC 长期不变或跳变BAT_DET、ITPOR、CFGUPMODE、电池参数、负载条件和算法更新节奏
偶发错误或任务阻塞包间隔、命令访问频率、时钟延展、有限超时、事务锁及中断中阻塞
上位机显示不可信数据有效状态、采样/接收时间、协议字节序和充电语义

本例已通过 C11 严格警告编译、Cortex-M3 独立编译和主机 AddressSanitizer/UndefinedBehaviorSanitizer 检查。八组模拟总线测试覆盖精确型号、访问时序、每个读取步骤失败时输出不变、SOH 单字节地址、符号边界、温度精度、范围拒绝和限速回绕。它们不替代真实 I²C 波形、芯片配置、电池充放电曲线与 SOC 精度测试;原文“±1%”“初始化 20 ms”“读取 5 ms”等数字缺少对应测量条件,不作为本实现的实测结论。