CW2015锂电池电量计驱动(ESP32,Arduino)

一、电池电量计的几种类型

1. 电压检测型电量计(Voltage-Based)

原理:通过测量电池电压来估算剩余电量(SoC)。
特点

  • 无需检流电阻,电路简单,成本低。

  • 精度较低(通常±5%~10%),受电池负载、温度、老化影响大。
    典型芯片

  • CW2015(赛微)

  • MAX170xx(Maxim)
    适用场景

  • 低功耗设备(如TWS耳机、智能手表)。

  • 对成本敏感且精度要求不高的应用。


2. 库仑计数型电量计(Coulomb Counting)

原理:通过测量充放电电流积分(库仑计数)计算电量。
特点

  • 精度较高(±1%~3%),但需外接检流电阻。

  • 需要初始校准(如满充容量)。

  • 长期使用会因电池老化产生累积误差。
    典型芯片

  • TI BQ系列(如BQ27545、BQ28Z610)

  • LTC294x(Linear Tech)
    适用场景

  • 智能手机、平板电脑。

  • 电动工具、无人机等需要高精度监测的场景。


3. 阻抗跟踪型电量计(Impedance Track, 混合算法)

原理:结合电压检测 + 库仑计数 + 电池阻抗模型,动态校准SoC。
特点

  • 精度最高(±1%以内),自动补偿电池老化、温度影响。

  • 需预存电池特性参数(如放电曲线)。
    典型芯片

  • TI BQ40Z50(支持多节电池)

  • BQ34Z100(适用于锂离子/铅酸电池)
    适用场景

  • 高端消费电子(如笔记本电池包)。

  • 电动汽车、储能系统等复杂环境。


4. 基于模型的电量计(Model-Based)

原理:通过电池数学模型(如Kalman滤波、神经网络)预测SoC。
特点

  • 依赖算法和处理器性能,灵活性高。

  • 常用于BMS(电池管理系统)中的软件方案。
    典型实现

  • 电动汽车BMS(如特斯拉的电池管理算法)。

  • 部分MCU内置的电池监测功能(如STM32的Battery Monitor)。


5. 其他类型

  • 温度补偿型:通过温度传感器修正电压/电流数据(如MAX1720x)。

  • 无线电量计:集成蓝牙/Wi-Fi传输数据(如TI BQ27220)。


选型关键因素

  1. 精度需求:高精度选阻抗跟踪型,低成本选电压检测型。

  2. 电池类型:锂离子、磷酸铁锂(LiFePO4)、铅酸等特性不同。

  3. 系统功耗:库仑计数器需持续供电,电压型可间歇工作。

  4. 封装与集成:是否需外接检流电阻、NTC等。


对比总结

类型精度复杂度成本适用场景
电压检测型低(±5%)TWS耳机、手环
库仑计数型中(±3%)智能手机、平板
阻抗跟踪型高(±1%)笔记本、电动汽车
模型算法型可变极高定制化BMS系统

根据具体需求(如成本、精度、功耗)选择合适类型即可。

二、CW2015简介

CW2015 是一款由赛微微电子(CellWise)推出的锂电池电量计芯片,主要用于单节锂离子/锂聚合物电池的电量监测(SoC, State of Charge)。它通过高精度电压检测和专利算法,实时估算电池剩余电量,并通过I²C接口输出数据,广泛应用于便携式电子设备。

CW2015属于电压检测型电量计,电路和使用相对简单

应用电路图:

三、驱动注意事项(重要)

1.关于电池模型

CW2015是需要电池模型的,在头文件中定义的64字节。

生成模型数据的具体算法或者软件我也没有找到,所以使用的是github找来的一个驱动里的,这是一个4.2V锂电池的模型。

如果你也用的4.2V锂电池,那是可以用用的,只是刚开始精度没有那么高。

因为CW2015是依靠电池端电压从而给出的电量,而且电量是一个百分比数值,所以不管你是用的多大容量的电池,都没有关系,模型一样可以用。

使用过程中CW2015会不断进行容量的矫正,要求不高的场合比如消费类电子产品是完全可以用的。要求高的场合,建议使用基于库伦检测原理的芯片,如LTC2944。

2.支持2节电池

CW2015官方资料支持2节锂电池,但是如何支持两节锂电池的资料较少。支持2节电池的应用中可能要考虑芯片的供电电压是否超最大值(手册上最高电压为6V)、写电池信息寄存器和新的电池模型等。

本驱动支持的是1节电池应用,如何支持2节请自行研究。

3.容量校准

实际应用中要通过手动启动校准和读取温度补偿等方法,使电池容量更为准确。

4.一些术语

//术语:
//  OCV (Open Circuit Voltage) - 开路电压:电池在无负载状态下的端电压
//  SOC (State of Charge) - 荷电状态:电池剩余电量百分比
//  RRT(剩余运行时间)
//  ATHD (低电量报警阈值设置)

四、驱动头文件CW2015.h

#ifndef __CW2015_H__
#define __CW2015_H__


//术语:
//  OCV (Open Circuit Voltage) - 开路电压:电池在无负载状态下的端电压
//  SOC (State of Charge) - 荷电状态:电池剩余电量百分比
//  RRT(剩余运行时间)
//  ATHD (低电量报警阈值设置)



#include <Wire.h>
#include <Arduino.h>

#define CW2015_ADDR             0x62

/* CW2015 Register Definitions */
#define REG_VERSION             0x00
#define REG_VCELL               0x02
#define REG_SOC                 0x04
#define REG_RRT_ALERT           0x06
#define REG_CONFIG              0x08
#define REG_MODE                0x0A
#define REG_BATINFO             0x10

/* Mode Masks */
#define MODE_SLEEP_MASK         (0x3<<6)
#define MODE_SLEEP              (0x3<<6)
#define MODE_NORMAL             (0x0<<6)
#define MODE_QUICK_START        (0x3<<4)
#define MODE_RESTART            (0xF<<0)

/* Config Flags */
#define CONFIG_UPDATE_FLG       (0x1<<1)
#define ATHD                    (0x0<<3)  // ATHD = 0%

/* Battery Parameters */
#define BATTERY_UP_MAX_CHANGE         720
#define BATTERY_DOWN_MIN_CHANGE       60
#define BATTERY_DOWN_MIN_CHANGE_SLEEP 1800
#define SIZE_BATINFO                  64

/* Battery configuration info - replace with your battery's profile */
static const uint8_t cw_bat_config_info[SIZE_BATINFO] = {
  0x15, 0x7E, 0x7C, 0x5C, 0x64, 0x6A, 0x65, 0x5C, 
  0x55, 0x53, 0x56, 0x61, 0x6F, 0x66, 0x50, 0x48,
  0x43, 0x42, 0x40, 0x43, 0x4B, 0x5F, 0x75, 0x7D,
  0x52, 0x44, 0x07, 0xAE, 0x11, 0x22, 0x40, 0x56,
  0x6C, 0x7C, 0x85, 0x86, 0x3D, 0x19, 0x8D, 0x1B,
  0x06, 0x34, 0x46, 0x79, 0x8D, 0x90, 0x90, 0x46,
  0x67, 0x80, 0x97, 0xAF, 0x80, 0x9F, 0xAE, 0xCB,
  0x2F, 0x00, 0x64, 0xA5, 0xB5, 0x11, 0xD0, 0x11
};

/* Battery Information Structure */
struct CW_Battery {
  uint8_t usb_online;
  uint32_t capacity;
  uint32_t voltage;
  uint8_t alt;
};

class CW2015 {
public:
  CW2015(TwoWire &wire = Wire, uint8_t sda = SDA, uint8_t scl = SCL);
  
  bool begin();                    // Initialize the device
  bool updateVoltage();            // Update battery voltage
  bool updateCapacity();           // Update battery capacity
  bool releaseAlert();             // Release ALRT pin after low battery interrupt
  bool setAthd(uint8_t new_athd);  // Set ATHD threshold
  
  // Get battery information
  uint32_t getVoltage() const { return battery.voltage; }
  uint32_t getCapacity() const { return battery.capacity; }
  bool isUSBPowered() const { return battery.usb_online; }
  bool hasAlert() const { return battery.alt; }
  
  void setUSBPowered(bool powered) { battery.usb_online = powered ? 1 : 0; }

  bool powerOnReset();
  bool config();
  bool updateConfigInfo();
  int getCapacity();
  int getVoltage();
  
private:
  TwoWire &_wire;
  uint8_t _sda;
  uint8_t _scl;
  
  CW_Battery battery;
  
  uint8_t no_charger_full_jump = 0;
  uint32_t allow_no_charger_full = 0;
  uint32_t allow_charger_always_zero = 0;
  uint8_t if_quickstart = 0;
  uint8_t reset_loop = 0;

  bool writeRegister(uint8_t reg, uint8_t value);
  bool readRegister(uint8_t reg, uint8_t *value);

};

extern CW2015 cw2015;  // Global instance

#endif

五、驱动源文件CW2015.cpp

#include "CW2015.h"



CW2015::CW2015(TwoWire &wire, uint8_t sda, uint8_t scl) 
  : _wire(wire), _sda(sda), _scl(scl) {
}

bool CW2015::begin() {
  _wire.begin(_sda, _scl);
  
  // Check if device responds
  _wire.beginTransmission(CW2015_ADDR);
  if (_wire.endTransmission() != 0) {
    Serial.println("CW2015 not found");
    return false;
  }
  
  // Initialize battery data
  battery.usb_online = 0;
  battery.capacity = 0;
  battery.voltage = 0;
  battery.alt = 0;
  
  // Configure the device
  if (!config()) {
    Serial.println("CW2015 configuration failed");
    return false;
  }
  
  Serial.println("CW2015 initialized");
  return true;
}

bool CW2015::writeRegister(uint8_t reg, uint8_t value) {
  _wire.beginTransmission(CW2015_ADDR);
  _wire.write(reg);
  _wire.write(value);
  return _wire.endTransmission() == 0;
}

bool CW2015::readRegister(uint8_t reg, uint8_t *value) {
  _wire.beginTransmission(CW2015_ADDR);
  _wire.write(reg);
  if (_wire.endTransmission(false) != 0) {
    return false;
  }
  
  if (_wire.requestFrom(CW2015_ADDR, (uint8_t)1) != 1) {
    return false;
  }
  
  *value = _wire.read();
  return true;
}

bool CW2015::powerOnReset() {
  if (!writeRegister(REG_MODE, MODE_SLEEP)) return false;
  delay(1);
  
  if (!writeRegister(REG_MODE, MODE_NORMAL)) return false;
  delay(1);
  
  return config();
}

bool CW2015::config() {
  uint8_t reg_val;
  
  // Wake up from sleep mode
  if (!writeRegister(REG_MODE, MODE_NORMAL)) {
    Serial.println("Wakeup fail");
    return false;
  }
  
  // Check ATHD
  if (!readRegister(REG_CONFIG, &reg_val)) return false;
  
  if ((reg_val & 0xF8) != ATHD) {
    reg_val &= 0x07;  // Clear ATHD
    reg_val |= ATHD;   // Set ATHD
    if (!writeRegister(REG_CONFIG, reg_val)) return false;
  }
  
  // Check config update flag
  if (!readRegister(REG_CONFIG, &reg_val)) return false;
  
  if (!(reg_val & CONFIG_UPDATE_FLG)) {
    Serial.println("Update flag for new battery info needed");
    if (!updateConfigInfo()) return false;
  } else {
    // Verify battery info
    uint8_t i;
    for (i = 0; i < SIZE_BATINFO; i++) {
      if (!readRegister(REG_BATINFO + i, &reg_val)) return false;
      if (cw_bat_config_info[i] != reg_val) break;
    }
    
    if (i != SIZE_BATINFO) {
      Serial.println("Update flag for new battery info needed (2)");
      if (!updateConfigInfo()) return false;
    }
  }
  
  // Check SOC
  for (uint8_t i = 0; i < 30; i++) {
    if (!readRegister(REG_SOC, &reg_val)) return false;
    if (reg_val <= 100) break;
    delay(100);
    
    if (i >= 29) {
      if (!writeRegister(REG_MODE, MODE_SLEEP)) return false;
      return false;
    }
  }
  
  return true;
}

bool CW2015::updateConfigInfo() {
  uint8_t reg_val;
  
  // Make sure not in sleep mode
  if (!readRegister(REG_MODE, &reg_val)) return false;
  if ((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP) return false;
  
  // Update battery info
  for (uint8_t i = 0; i < SIZE_BATINFO; i++) {
    if (!writeRegister(REG_BATINFO + i, cw_bat_config_info[i])) return false;
  }
  
  // Verify battery info
  for (uint8_t i = 0; i < SIZE_BATINFO; i++) {
    if (!readRegister(REG_BATINFO + i, &reg_val)) return false;
    if (reg_val != cw_bat_config_info[i]) return false;
  }
  
  // Set config flags
  if (!readRegister(REG_CONFIG, &reg_val)) return false;
  reg_val |= CONFIG_UPDATE_FLG;  // Set UPDATE_FLAG
  reg_val &= 0x07;               // Clear ATHD
  reg_val |= ATHD;               // Set ATHD
  if (!writeRegister(REG_CONFIG, reg_val)) return false;
  
  // Reset
  if (!writeRegister(REG_MODE, MODE_RESTART)) return false;
  delay(1);
  if (!writeRegister(REG_MODE, MODE_NORMAL)) return false;
  
  return true;
}

bool CW2015::setAthd(uint8_t new_athd) {
  uint8_t reg_val;
  if (!readRegister(REG_CONFIG, &reg_val)) return false;
  
  new_athd = new_athd << 3;
  reg_val &= 0x07;     // Clear ATHD
  reg_val |= new_athd; // Set new ATHD
  
  return writeRegister(REG_CONFIG, reg_val);
}

int CW2015::getCapacity() {
  uint8_t reg_val;
  uint8_t cw_capacity;
  
  if (!readRegister(REG_SOC, &reg_val)) return -1;
  cw_capacity = reg_val;
  
  if ((cw_capacity < 0) || (cw_capacity > 100)) {
    reset_loop++;
    if (reset_loop > 5) {
      if (!powerOnReset()) return -1;
      reset_loop = 0;
    }
    return battery.capacity;
  } else {
    reset_loop = 0;
  }
  
  if (((battery.usb_online == 1) && (cw_capacity == (battery.capacity - 1))) ||
      ((battery.usb_online == 0) && (cw_capacity == (battery.capacity + 1)))) {
    if (!((cw_capacity == 0 && battery.capacity <= 2) || 
          (cw_capacity == 100 && battery.capacity == 99))) {
      cw_capacity = battery.capacity;
    }
  }
  
  if ((battery.usb_online == 1) && (cw_capacity >= 95) && (cw_capacity <= battery.capacity)) {
    // Avoid not charging full
    allow_no_charger_full++;
    if (allow_no_charger_full >= BATTERY_UP_MAX_CHANGE) {
      uint8_t allow_capacity = battery.capacity + 1;
      cw_capacity = (allow_capacity <= 100) ? allow_capacity : 100;
      no_charger_full_jump = 1;
      allow_no_charger_full = 0;
    } else if (cw_capacity <= battery.capacity) {
      cw_capacity = battery.capacity;
    }
  } 
  else if ((battery.usb_online == 0) && (cw_capacity <= battery.capacity) && 
           (cw_capacity >= 90) && (no_charger_full_jump == 1)) {
    // Avoid battery level jump
    if (battery.usb_online == 0) {
      allow_no_charger_full++;
    }
    if (allow_no_charger_full >= BATTERY_DOWN_MIN_CHANGE) {
      uint8_t allow_capacity = battery.capacity - 1;
      allow_no_charger_full = 0;
      if (cw_capacity >= allow_capacity) {
        no_charger_full_jump = 0;
      } else {
        cw_capacity = (allow_capacity > 0) ? allow_capacity : 0;
      }
    } else if (cw_capacity <= battery.capacity) {
      cw_capacity = battery.capacity;
    }
  } else {
    allow_no_charger_full = 0;
  }
  
  if ((battery.usb_online > 0) && (cw_capacity == 0)) {
    allow_charger_always_zero++;
    if ((allow_charger_always_zero >= BATTERY_DOWN_MIN_CHANGE_SLEEP) && 
        (if_quickstart == 0)) {
      if (!powerOnReset()) return -1;
      if_quickstart = 1;
      allow_charger_always_zero = 0;
    }
  } 
  else if ((if_quickstart == 1) && (battery.usb_online == 0)) {
    if_quickstart = 0;
  }
  
  return cw_capacity;
}

int CW2015::getVoltage() {
  uint32_t ad_value = 0;
  uint32_t ad_buff = 0;
  uint32_t ad_value_min = 0;
  uint32_t ad_value_max = 0;
  uint8_t reg_val[2];
  
  for (uint8_t i = 0; i < 3; i++) {
    if (!readRegister(REG_VCELL, &reg_val[0])) return -1;
    if (!readRegister(REG_VCELL + 1, &reg_val[1])) return -1;
    
    ad_buff = (reg_val[0] << 8) + reg_val[1];
    
    if (i == 0) {
      ad_value_min = ad_buff;
      ad_value_max = ad_buff;
    }
    
    if (ad_buff < ad_value_min) ad_value_min = ad_buff;
    if (ad_buff > ad_value_max) ad_value_max = ad_buff;
    
    ad_value += ad_buff;
  }
  
  ad_value -= ad_value_min;
  ad_value -= ad_value_max;
  ad_value = ad_value * 305 / 1000;
  
  return ad_value;
}

bool CW2015::releaseAlert() {
  uint8_t reg_val;
  if (!readRegister(REG_RRT_ALERT, &reg_val)) return false;
  
  battery.alt = reg_val & 0x80;
  reg_val &= 0x7F;
  
  return writeRegister(REG_RRT_ALERT, reg_val);
}

bool CW2015::updateCapacity() {
  int capacity = getCapacity();
  if (capacity == -1) return false;
  
  if ((capacity >= 0) && (capacity <= 100) && (battery.capacity != capacity)) {
    battery.capacity = capacity;
  }
  return true;
}

bool CW2015::updateVoltage() {
  int voltage = getVoltage();
  if (voltage == -1) return false;
  
  if (voltage == 1) {
    // Keep previous voltage
  } else if (battery.voltage != voltage) {
    battery.voltage = voltage;
  }
  return true;
}

六、使用方法

1.全局定义

#define I2C_SDA_PinNum 5
#define I2C_SCL_PinNum 4

CW2015 cw2015(Wire1, I2C_SDA_PinNum, I2C_SCL_PinNum);  // Global instance

2.初始化

  //Wire1.begin(I2C_SDA_PinNum, I2C_SCL_PinNum,150000);
  if (!cw2015.begin()) {
    Serial.println("Failed to initialize CW2015");
    while (1); // Halt if initialization fails
  }
  else{
    Serial.println("CW2015 init OK!");
  }

第一行总线的初始化视实际情况。

3.数据读取

  // Update battery information
  cw2015.updateVoltage();
  cw2015.updateCapacity();
  
  // Print battery info
  Serial.print("Voltage: ");
  Serial.print(cw2015.getVoltage());
  Serial.print(" mV, Capacity: ");
  Serial.print(cw2015.getCapacity());
  Serial.println("%");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值