[单片机框架][drivers层][cw2015] fuelgauge 硬件电量计(二)
接上一篇:[单片机框架][device层] fuelgauge 电量计
[单片机框架][drivers层][cw2015] fuelgauge 硬件电量计(一)
本章是硬件电量计的使用方法,采用IIC通信。利用opt的方式操作函数读写。
I2C接口
CW2015 通过 I2C 接口进行通信。 I2C 是一种两线漏极开路接口,支持单个总线上的多个设备和主机。 一些 I2C 设备可以作为主设备或从设备,但是CW2015 只能作为从设备,只将总线拉低,从不驱动总线为高。
在标准模式下,I2C 总线上的数据可以以高达 100kbps 的速率传输。 还支持 I2C 快速模式 (400kbps) 或快速模式 plus (1 Mbps)。
设备地址
I2C 设备地址由 7 位从地址和 1 个读/写控制位组成。
CW2015 的地址固定在 0b1100010。 结合 R/W 位:
CW2015的读命令为0xC5;
CW2015 的写命令为 0xC4。
cw2015.c
#include <string.h>
#include "log.h"
#include "errorno.h"
#include "modules.h"
#include "battery/batt_model.h"
#include "sys_cmsis.h"
#include "bsp_i2c.h"
#include "fuelgauge.h"
#define CW2015_VOLTAGE_RESOLUTION 305/1000//0.305mV
#define REG_VERSION 0x0
#define REG_VCELL 0x2
#define REG_SOC 0x4
#define REG_RRT_ALERT 0x6
#define REG_CONFIG 0x8
#define REG_MODE 0xA
#define REG_BATINFO 0x10
#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)
#define CONFIG_UPDATE_FLG (0x1<<1)
#define ATHD (0x0<<3)
#define ATHD_CLEAR_BIT_AREA 0x07
#define BATTERY_DOWN_MIN_CHANGE_SLEEP 1800
#define FUELGAGUE_INIT_RETRY_TIMES 3
typedef struct {
uint8_t usb_plugin;
uint32_t soc;
uint32_t volt;
uint8_t alt;
} batt_info_t;
batt_info_t batt_info;
i2c_transfer_t i2c2_transfer = {
.bus = BSP_I2C_BUS2,
.slave_addr = 0x62,
};
uint32_t allow_charger_always_zero = 0;
uint8_t if_quick_start = 0;
uint8_t reset_loop = 0;
static sys_timer_id_t g_gauge_timer_id;
static sys_timer_def_t g_gauge_timer;
static fuelgauge_info_t g_fg_info;
static fuelgauge_charge_state_t g_set_charge_state = FUELGAUGE_CHARGE_STATE_DISCHARGE;
static uint16_t g_set_current = 0;
static void fuelgague_cw2015_delay10us(volatile uint32_t us) {
volatile uint32_t i;
for (i = 0; i < us; i++);
}
static int32_t fuelgague_cw2015_cfg_batt_model(void) {
int32_t ret = 0;
uint8_t i;
uint8_t reset_val;
uint8_t reg_val;
/* make sure no in sleep mode */
ret = bsp_i2c_read(&i2c2_transfer, REG_MODE, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
if ((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP)
return RETVAL(E_STATE);
/* update new battery info */
for (i = 0; i < SIZE_BATINFO; i++) {
reg_val = batt_model[i];
ret = bsp_i2c_write(&i2c2_transfer, REG_BATINFO+i, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
}
/* readback & check */
for (i = 0; i < SIZE_BATINFO; i++) {
ret = bsp_i2c_read(&i2c2_transfer, REG_BATINFO + i, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
if (reg_val != batt_model[i])
return RETVAL(E_SET);
}
/* set cw2015/cw2013 to use new battery info */
ret = bsp_i2c_read(&i2c2_transfer, REG_CONFIG, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
reg_val |= CONFIG_UPDATE_FLG; /* set UPDATE_FLAG */
reg_val &= ATHD_CLEAR_BIT_AREA; /* clear ATHD */
reg_val |= ATHD; /* set ATHD */
ret = bsp_i2c_write(&i2c2_transfer, REG_CONFIG, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
/* reset */
reset_val = MODE_NORMAL;
reg_val = MODE_RESTART;
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
fuelgague_cw2015_delay10us(1000);
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, &reset_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
fuelgague_cw2015_delay10us(1000);
return RETVAL(E_OK);
}
static int32_t gague_cw2015_cfg(void) {
int32_t ret;
uint8_t i;
uint8_t reg_val = MODE_NORMAL;
/* wake up cw2015/13 from sleep mode */
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
/* check ATHD if not right */
ret = bsp_i2c_read(&i2c2_transfer, REG_CONFIG, ®_val, 1);//
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
if ((reg_val & 0xf8) != ATHD) {
/* the new ATHD need set */
/* clear ATHD */
reg_val &= 0x07;
/* set ATHD */
reg_val |= ATHD;
ret = bsp_i2c_write(&i2c2_transfer, REG_CONFIG, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
}
/* check config_update_flag if not right */
ret = bsp_i2c_read(&i2c2_transfer, REG_CONFIG, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
if(!(reg_val & CONFIG_UPDATE_FLG)) {
/* update flag for new battery info need set */
ret = fuelgague_cw2015_cfg_batt_model();
if (ret != RETVAL(E_OK))
return ret;
} else {
for(i = 0; i < SIZE_BATINFO; i++) {
ret = bsp_i2c_read(&i2c2_transfer, REG_BATINFO +i, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
if (batt_model[i] != reg_val)
break;
}
if (i != SIZE_BATINFO) {
//"update flag for new battery info need set"
ret = fuelgague_cw2015_cfg_batt_model();
if (ret != 0)
return ret;
}
}
return RETVAL(E_OK);
}
static int32_t fuelgague_cw2015_reboot(void) {
int32_t ret = 0;
uint8_t reset_val = MODE_SLEEP;
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, &reset_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
fuelgague_cw2015_delay10us(10);
reset_val = MODE_NORMAL;
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, &reset_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
fuelgague_cw2015_delay10us(10);
ret = gague_cw2015_cfg();
if (ret != 0)
return ret;
return RETVAL(E_OK);
}
static int32_t fuelgague_cw2015_get_vol(void) {
int32_t ret = 0;
uint8_t get_ad_times = 0;
uint8_t reg_val[2] = {0 , 0};
unsigned long ad_value = 0;
uint32_t ad_buff = 0;
uint32_t ad_value_min = 0;
uint32_t ad_value_max = 0;
for(get_ad_times = 0; get_ad_times < 3; get_ad_times++) {
ret = bsp_i2c_read(&i2c2_transfer, REG_VCELL, ®_val[0],2);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
ad_buff = (reg_val[0] << 8) + reg_val[1];
if (get_ad_times == 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 * CW2015_VOLTAGE_RESOLUTION;
return(ad_value);
}
static int32_t fuelgague_cw2015_get_soc(void) {
int32_t ret = 0;
uint8_t reg_val;
uint8_t cw_soc;
ret = bsp_i2c_read(&i2c2_transfer, REG_SOC, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
cw_soc = reg_val;
if ((cw_soc < 0) || (cw_soc > 100)) {
reset_loop ++;
if (reset_loop > 5) {
ret = fuelgague_cw2015_reboot();
if(ret != 0)
return ret;
reset_loop = 0;
}
return batt_info.soc;
}
else
reset_loop = 0;
if ((batt_info.usb_plugin > 0) && (cw_soc == 0)) {
allow_charger_always_zero ++;
if ((allow_charger_always_zero >= BATTERY_DOWN_MIN_CHANGE_SLEEP) && (if_quick_start == 0)) {
ret = fuelgague_cw2015_reboot();
if (ret != 0)
return ret;
if_quick_start = 1;
allow_charger_always_zero = 0;
}
}
else if ((if_quick_start == 1)&&(batt_info.usb_plugin == 0))
if_quick_start = 0;
return(cw_soc);
}
static int32_t fuelgague_cw2015_update_vol(void) {
uint32_t cw_volt;
cw_volt = fuelgague_cw2015_get_vol();
if (cw_volt == 1)
batt_info.volt = batt_info.volt;
else if (batt_info.volt != cw_volt)
batt_info.volt = cw_volt;
return batt_info.volt;
}
static int32_t fuelgague_cw2015_update_soc(void) {
int32_t cw_soc;
cw_soc = fuelgague_cw2015_get_soc();
if ((cw_soc >= 0) && (cw_soc <= 100) && (batt_info.soc != cw_soc)) {
batt_info.soc = cw_soc;
return batt_info.soc;
}
else {
return RETVAL(E_FAIL);
}
}
static void fuelgague_info_reset(void) {
batt_info.usb_plugin = 0;
batt_info.soc = 0;
batt_info.volt = 0;
batt_info.alt = 0;
}
static void gague_cw2015_init(void) {
int32_t ret = 0;
uint8_t retry_times = 0;
for (retry_times = 0; retry_times < FUELGAGUE_INIT_RETRY_TIMES; retry_times ++) {
ret = gague_cw2015_cfg();
if (ret == RETVAL(E_OK))
break;
}
switch (ret)
{
case RETVAL(E_BUS):
{
LOG_D("[fuelgague][init][error]i2c bus err\r\n");
}
break;
case RETVAL(E_STATE):
{
LOG_D("[fuelgague][init][error]ic is sleeping\r\n");
}
break;
case RETVAL(E_SET):
{
LOG_D("[fuelgague][init][error]cfg info set fail\r\n");
}
break;
default:
break;
}
fuelgague_info_reset();
}
static void gauge_timer_power_stable_check(void const *arg) {
g_fg_info.state = g_set_charge_state;
g_fg_info.current = g_set_current;
}
static void gauge_process(void) {
int32_t ret = 0;
g_fg_info.ccv = fuelgague_cw2015_update_vol();
ret = fuelgague_cw2015_update_soc();
if (ret != RETVAL(E_FAIL))
g_fg_info.soc = ret;
}
static int32_t gauge_set_power(fuelgauge_driver_t *drv, fuelgauge_work_mode_t mode) {
return RETVAL(E_OK);
}
static int32_t gauge_init(void) {
memset(&g_fg_info, 0, sizeof(g_fg_info));
g_gauge_timer.ptimer = gauge_timer_power_stable_check;
g_gauge_timer_id = sys_timer_create(&g_gauge_timer, SYS_TIMER_ONCE, NULL);
if (!g_gauge_timer_id)
return RETVAL(E_FAIL);
uint32_t temp;
temp = sys_enter_critical();
gague_cw2015_init();
sys_exit_critical(temp);
return RETVAL(E_OK);
}
int32_t gauge_get_info(fuelgauge_driver_t *drv, fuelgauge_info_t *info) {
gauge_process();
info->ccv = g_fg_info.ccv;
info->soc = g_fg_info.soc;
return RETVAL(E_OK);
}
static int32_t gague_set_batt_type(fuelgauge_driver_t *drv, uint32_t type) {
int32_t ret;
ret = fuelgague_cw2015_cfg_batt_model();
if (ret != RETVAL(E_OK)) {
LOG_E("[fuelgague][driver]:fuelgague cfg batt model fail %d\r\n", ret);
return ret;
}
return RETVAL(E_OK);
}
static int32_t gauge_set_charge_state(fuelgauge_driver_t *drv, fuelgauge_charge_state_t state, uint32_t current) {
if (state == FUELGAUGE_CHARGE_STATE_CHARGE)
batt_info.usb_plugin = 1;
else if (state == FUELGAUGE_CHARGE_STATE_DISCHARGE)
batt_info.usb_plugin = 0;
if ((state == g_fg_info.state) && (current == g_fg_info.current))
return RETVAL(E_OK);
g_set_charge_state = state;
g_set_current = current;
sys_timer_stop(g_gauge_timer_id);
sys_timer_start(g_gauge_timer_id, 3000);
return RETVAL(E_OK);
}
static int32_t gauge_set_work_mode(fuelgauge_driver_t *drv, fuelgauge_work_mode_t mode) {
int32_t ret;
uint8_t reg_val = 0;
switch (mode)
{
case FUELGAUGE_WORK_MODE_NORMAL:
{
reg_val = MODE_NORMAL;
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
}
break;
case FUELGAUGE_WORK_MODE_SLEEP:
{
reg_val = MODE_SLEEP;
ret = bsp_i2c_write(&i2c2_transfer, REG_MODE, ®_val, 1);
if (ret == RETVAL(E_BUS))
return RETVAL(E_BUS);
}
break;
default:
break;
}
return RETVAL(E_OK);
}
static int32_t gauge_probe(void) {
return RETVAL(E_OK);
}
static int32_t gauge_remove(void) {
return RETVAL(E_OK);
}
static int32_t gauge_shutdown(driver_t *drv) {
return RETVAL(E_OK);
}
static int32_t gauge_suspend(driver_t *drv) {
return RETVAL(E_OK);
}
static int32_t gauge_resume(driver_t *drv) {
return RETVAL(E_OK);
}
static driver_pm_ops_t gauge_pm =
{
.shutdown = gauge_shutdown,
.suspend = gauge_suspend,
.resume = gauge_resume,
};
static fuelgauge_driver_t gauge = {
.drv = {
.probe = gauge_probe,
.remove = gauge_remove,
.pm_ops = &gauge_pm,
},
.ops = {
.init = gauge_init,
.get_info = gauge_get_info,
.set_batt_type = gague_set_batt_type,
.set_charge_state = gauge_set_charge_state,
.set_work_mode = gauge_set_work_mode,
.set_power = gauge_set_power,
}
};
static void gauge_register(void) {
int32_t ret;
ret = fuelgauge_driver_register("cw2015", &gauge);
if (ret != RETVAL(E_OK)) {
}
}
DRIVER_INITCALL("cw2015", gauge_register);
batt_model.h 这个模型数据一般由电池厂商提供
#ifndef __BATT_MODEL_H__
#define __BATT_MODEL_H__
#define SIZE_BATINFO 64
uint8_t batt_model[SIZE_BATINFO] =
{
0x17, 0xC8, 0x62, 0x64, 0x69, 0x66, 0x66,
0x65, 0x60, 0x5E, 0x5A, 0x5E, 0x65, 0x4C,
0x42, 0x41, 0x38, 0x31, 0x2A, 0x32, 0x37,
0x41, 0x47, 0x46, 0x1D, 0x99, 0x06, 0x66,
0x27, 0x47, 0xA9, 0x97, 0x9E, 0xA5, 0x9C,
0x9E, 0x40, 0x1B, 0x64, 0x53, 0x0C, 0x61,
0x10, 0x42, 0x7B, 0x96, 0xA4, 0x30, 0x4F,
0x78, 0x9B, 0xC3, 0x80, 0x97, 0x8B, 0xFF,
0x2F, 0x00, 0x64, 0xA5, 0xB5, 0xC1, 0x50,
0x77
};
#endif // __BATT_MODEL_H__