980578873 发表于 2023-3-1 14:44:50

关于充放电电量指示灯不一致的问题

大家好,我现在使用STC8H1K08设计一个充电盒子,盒子内置一颗可充电的锂电池,容量是500mAh,盒子外面有4个LED电量指示灯,使用单片机内置的ADC检测电池电压,当盒子插入USB 5V充电时,根据电池电量的高低就会显示的对应LED,电池电量越高显示(打开)的LED就越多,反之,越少。当盒子不充电(放电)时,可通过短按盒子上面的按键显示当前电池的电量指示灯。我现在遇到的问题是:盒子充电时和不充电时(放电时)电量指示灯显示不一致的问题,比如盒子在充电时显示两个LED,盒子拔掉USB 5V不充电时通过短按按键却显示3个电量指示灯。我认为正确的做法是盒子充放电显示电量指示灯的数量应该要一致。现向大家请教一下怎么解决此问题,如下是设计资料,谢谢各位指点。

如下是盒子在充电时的灯显程序:

                  if(usb_in==1)
                        {       
                                vbat=get_vbat_value(0);
                                  if(vbat>4.0947)
                        {
                                if(count2==50)//500mS时间到
                                {
                                  led1=1;
                                led2=1;
                                led3=1;
                                led4=!led4;
                                  count2=0;
                                }
                        }
                        else if(vbat>3.9725)
                        {
                                if(count2==50)
                                {
                                led1=1;
                                led2=1;
                                led3=!led3;
                                led4=0;
                                count2=0;
                                }
                        }
                        else if(vbat>3.9105)
                        {
                                if(count2==50)
                                {
                                led1=1;
                                led2=!led2;
                                led3=0;
                                led4=0;
                                count2=0;
                                }
                        }
                        else if(vbat>3.0)
                        {
                                if(count2==50)
                                {
                                led1=!led1;
                                led2=0;
                                led3=0;
                                led4=0;
                                count2=0;
                                }
                        }

        }       

如下是盒子在不充电时(放电时),通过按键的灯显程序:

        if(key_flag==1&&usb_out==1)
                {
                        vbat=get_vbat_value(0);
                        if(vbat<3.5001)
                        {
                                if(count3==50)//盒子在非常低电时会快闪led提示
                                {
                                  led1=!led1;
                                  led2=0;
                                  led3=0;
                                  led4=0;
                               count3=0;
                               key_flag=0;
                                }
                }
                                else if(vbat<3.6954)
                        {
                                led1=1;
                                led2=0;
                                led3=0;
                                led4=0;
                                if(count3==300)//显示3秒电量指示灯
                                {
                                  led1=0;
                                  led2=0;
                                  led3=0;
                                  led4=0;
                                count3=0;
                                key_flag=0;
                                }
                }
                        else if(vbat<3.7589)
                        {
                                led1=1;
                                led2=1;
                                led3=0;
                                led4=0;
                                if(count3==300)
                                {
                                  led1=0;
                                  led2=0;
                                  led3=0;
                                  led4=0;
                                count3=0;
                                key_flag=0;
                                }
                }
                        else if(vbat<3.8929)
                        {
                                led1=1;
                                led2=1;
                                led3=1;
                                led4=0;
                                if(count3==300)
                                {
                               led1=0;
                                  led2=0;
                                  led3=0;
                                  led4=0;
                                count3=0;
                                key_flag=0;
                                }
                        }
                        else if(vbat<4.2)
                        {
                                led1=1;
                                led2=1;
                                led3=1;
                                led4=1;
                                if(count3==300)
                                {
                                  led1=0;
                                  led2=0;
                                  led3=0;
                                  led4=0;
                                count3=0;
                                key_flag=0;
                                }
                        }       
        }

如下是电池充放电曲线图:


如下是根据电池充放电划分的电压和电量之间的对应关系


如下是ADC检测电池电压的线路图:





ziyueboy 发表于 2023-3-2 14:57:37

不做差分么?基准2v差分到0-2.5v,完事adc准一些。8h1k10位ad.电池电压变化范围是2.5-4.2v,1.7你在分压,分辨率不够吧。

梁工 发表于 2023-3-2 19:01:29

楼主,你可以检测一下电池充电和不充电时的端电压,还有放电时的端电压。
因为电池有内阻(包括保护板的电阻),所以以上3中状态下,电池端电压是不同的,据此显示就会不同,否则就要根据电流来修正。
比较好的方式是用库仑计的方式显示(即实际电量,mAH)。

980578873 发表于 2023-3-2 19:44:32

梁工 发表于 2023-3-2 19:01
楼主,你可以检测一下电池充电和不充电时的端电压,还有放电时的端电压。
因为电池有内阻(包括保护板的电 ...

梁工,您好。3种状态下确实是不一样的,它们的关系:电池充电时的电压>电池不充电时的电压>电池放电时的电压。其实可以概括为两种状态充电盒放电(不充电),它们的关系是,电池充电时的电压>电池放电时的电压。为了降低设计成本,现在不想使用库伦计,参照电池的充放电曲线,电量不用计算那么精确,用单片机内置的ADC可否解决问题?

980578873 发表于 2023-3-2 19:46:22

ziyueboy 发表于 2023-3-2 14:57
不做差分么?基准2v差分到0-2.5v,完事adc准一些。8h1k10位ad.电池电压变化范围是2.5-4.2v,1.7你在分压,分 ...

电池电压是单端电压,不用做差分吧,10位AD,分辨率已足够

梁工 发表于 2023-3-2 22:30:04

980578873 发表于 2023-3-2 19:44
梁工,您好。3种状态下确实是不一样的,它们的关系:电池充电时的电压>电池不充电时的电压>电池放电时 ...

如果有检测电流,则根据电池的内阻*电流进行修正电压,来判断电池电压,否则解决不了,因为ADC读到的电压就不是电池真正的电压,叠加了内阻压降了。

980578873 发表于 2023-3-3 09:58:05

梁工 发表于 2023-3-2 22:30
如果有检测电流,则根据电池的内阻*电流进行修正电压,来判断电池电压,否则解决不了,因为ADC读到的电压 ...

梁工,我看到有大佬能解决此问题,没有电流检测,就是通过单片机内置的ADC检测电池电压然后再结合代码算法就能解决问题。

梁工 发表于 2023-3-3 11:51:14

980578873 发表于 2023-3-3 09:58
梁工,我看到有大佬能解决此问题,没有电流检测,就是通过单片机内置的ADC检测电池电压然后再结合代码算 ...

那能否贴出我也学习一下?
只测电池电压、不测电流 就能通过算法抵消电池内阻、保护板内阻的影响,我倒是第一次听说,这么好的算法我真的要学习下。

980578873 发表于 2023-3-3 19:40:21

梁工 发表于 2023-3-3 11:51
那能否贴出我也学习一下?
只测电池电压、不测电流 就能通过算法抵消电池内阻、保护板内阻的影响,我倒是 ...

梁工,贴代码出来了,我没看懂,没办法模仿
/******************************************************************************/
/** \file adc_detection.c
**
** ADC detection.
**
**   - 2019-08-21.0By Johnny.Cai
**
******************************************************************************/
/*******************************************************************************
* Include files
******************************************************************************/
#include "adc_detection.h"
extern type_stc_user_system_t gb_user_system;
/*******************************************************************************
* Local pre-processor symbols/macros ('#define')
******************************************************************************/
const uint16_t bat_gauge_level = {
    3300, 3488, 3536, 3571, 3603, 3643, 3701, 3777, 3867, 3966, 4098
};/*3401*///20200928
const uint16_t bat_gauge_level2 = {
    3614, 3671, 3702, 3733, 3766, 3814, 3842, 3971, 4072, 4187, 4210    //20201105
};
uint32_t        sum_bat_adc;
uint16_t        gauge_buffer, gauge_buffer_count = 0;
uint8_t                bat_adc_initial = 0;


/*******************************************************************************
* Global variable definitions (declared in header file with 'extern')         *
******************************************************************************/

/*******************************************************************************
* Local type definitions ('typedef')
******************************************************************************/

/*******************************************************************************
* Local function prototypes ('static')
******************************************************************************/

/*******************************************************************************
* Local variable definitions ('static')
******************************************************************************/
type_stc_adc_detection lc_adc_detection;
/*******************************************************************************
* Function implementation - global ('extern') and local ('static')
******************************************************************************/
/******************************************************************************
** \brief    AdcInit
**
** \param null
**
** \return null
******************************************************************************/
void AdcInit(void)
{
    stc_adc_cfg_t       stcAdcCfg;
    stc_adc_cont_cfg_tstcAdcContCfg;

    DDL_ZERO_STRUCT(stcAdcCfg);
    DDL_ZERO_STRUCT(stcAdcContCfg);

    Gpio_SetAnalog(CURRENT_RIGHT_PORT, CURRENT_RIGHT_PIN, TRUE);
    Gpio_SetAnalog(CURRENT_LEFT_PORT, CURRENT_LEFT_PIN, TRUE);
    Gpio_SetAnalog(VOLTAGE_BAT_PORT, VOLTAGE_BAT_PIN, TRUE);


    Adc_Enable();
    M0P_BGR->CR_f.BGR_EN = 0x1u;//BGR enable
    M0P_BGR->CR_f.TS_EN = 0x0u;   //internal temperature disable
    delay100us(1);

    stcAdcCfg.enAdcOpMode = AdcContMode;                //continue mode
    stcAdcCfg.enAdcClkSel = AdcClkSysTDiv4;             //PCLK/4
    stcAdcCfg.enAdcSampTimeSel = AdcSampTime12Clk;      //12 clocks for sample
    stcAdcCfg.enAdcRefVolSel = RefVolSelInBgr1p5;       //refrence: internal 2.5V(avdd>3V,SPS<=200kHz)
    //stcAdcCfg.enAdcRefVolSel = RefVolSelAVDD;         //refrence: AVDD
    stcAdcCfg.bAdcInBufEn = FALSE;                     //voltage follower enable, SPS(sample rate) must <=200K
    stcAdcCfg.enAdcTrig0Sel = AdcTrigDisable;
    stcAdcCfg.enAdcTrig1Sel = AdcTrigDisable;
    Adc_Init(&stcAdcCfg);

    stcAdcContCfg.enAdcContModeCh = VOLTAGE_BAT_CHN;    //channel BAT
    stcAdcContCfg.u8AdcSampCnt = 0x0Fu;               //sample timers(timers = 0x0F+1)
    stcAdcContCfg.bAdcResultAccEn = TRUE;               //enable ACC
    Adc_ConfigContMode(&stcAdcCfg, &stcAdcContCfg);
}

void AdcUninit(void)
{
    M0P_BGR->CR_f.BGR_EN = 0x0u;
    Adc_Disable();
}
/******************************************************************************
** \brief    ADCSample
**
** \param ch                         sample channel
**
** \return u32adcresult                  ADC result
******************************************************************************/
uint32_t ADCSample(en_adc_samp_ch_sel_t ch)
{
    uint32_t u32adcresult;
    M0P_ADC->CR2 &= 0xFFFFFF00;
    M0P_ADC->CR2 |= 1 << ch;
    M0P_ADC->CR0_f.SEL = ch;
    delay100us(1);

    Adc_Start();                               //ADC start

    while(FALSE == M0P_ADC->IFR_f.CONT_INTF);//wait for ADC complete
    M0P_ADC->ICLR_f.CONT_INTC = 0;             //clear the flag

    Adc_GetAccResult(&u32adcresult);
    Adc_ClrAccResult();

    return u32adcresult;
}

/******************************************************************************
** \brief    HandsetLCurrent
**
** \param    null
**
** \return   current                  max=255mA
******************************************************************************/
uint8_t HandsetLCurrent(void)
{
    uint32_t temp;

    temp = ADCSample(CURRENT_LEFT_CHN);

    temp *= REFERENCE_ADC;
    temp /= CURRENT_RESISTANCE;
    temp /= 65536;

    return (uint8_t)temp;
}

/******************************************************************************
** \brief    HandsetRCurrent
**
** \param    null
**
** \return   current                  max=255mA
******************************************************************************/
uint8_t HandsetRCurrent(void)
{
    uint32_t temp;

    temp = ADCSample(CURRENT_RIGHT_CHN);

    temp *= REFERENCE_ADC;
    temp /= CURRENT_RESISTANCE;
    temp /= 65536;



    return (uint8_t)temp;
}

/******************************************************************************
** \brief    BatGauge
**
** \param    null
**
** \return   gauge                  max=100
******************************************************************************/
uint8_t BatGauge(uint8_t charge)
{
    uint32_t temp;
    uint8_t level;
    const uint16_t *vaule_level;

    if(charge)
    {
      vaule_level = bat_gauge_level2;
    }
    else
    {
      vaule_level = bat_gauge_level;
    }


    temp = ADCSample(VOLTAGE_BAT_CHN);

    temp *= REFERENCE_ADC / 4;
    temp *= VOLTAGE_DIVIDE_TOTAL;
    temp /= VOLTAGE_DIVIDE_MODULUS;
    temp /= 16384;

    if(temp < vaule_level)
      temp = vaule_level;

    gauge_buffer = temp;                //递推滤波算法 191226f
    if(gauge_buffer_count == BUFFER_NUM)
    {
      gauge_buffer_count = 0;
    }
    sum_bat_adc = 0;
    for(uint8_t loop = 0; loop < BUFFER_NUM; loop++)
    {
      sum_bat_adc += gauge_buffer;
    }
    temp = sum_bat_adc / BUFFER_NUM;

    if(temp < vaule_level)
      temp = vaule_level;

    /****************************************************/
    for(level = 1; level <= 10; level++)
    {
      if(temp < vaule_level)
      {
            temp = vaule_level - temp;
            temp = temp * 10 / (vaule_level - vaule_level);
            temp = level * 10 - temp;

            return (uint8_t)temp;
      }
    }
    return 100;
}

/******************************************************************************
** \brief    BatDetect
**
** \param    charge                              TRUE:battery charging
**
** \return   gauge of battery                  max=100
******************************************************************************/
uint8_t BatDetect(uint8_t charge)
{
    uint8_t gauge = BatGauge(charge);    //get the bat gauge

    if(charge)
    {
      lc_adc_detection.flag_f.bat_low = 0;
      lc_adc_detection.flag_f.bat_none = 0;
      lc_adc_detection.bat_low_cnt = 0;
      lc_adc_detection.bat_none_cnt = 0;

      if(gb_user_system.bat_th_gauge < 80)
      {
            if(gb_user_system.bat_th_gauge < gauge)
            {
                if(gauge > 80)
                {
                  gb_user_system.bat_th_gauge = 80;
                }
                else
                {
                  gb_user_system.bat_th_gauge = gauge;
                }
            }
      }

      if(gb_user_system.bat_th_gauge < 90)
      {
            if(++lc_adc_detection.gauge_charge_cnt >= GAUGE_LOW_CHARGE_UP_TIME)
            {
                lc_adc_detection.gauge_charge_cnt = 0;
                gb_user_system.bat_th_gauge++;
            }
      }
      else
      {
            if(++lc_adc_detection.gauge_charge_cnt >= GAUGE_HIGH_CHARGE_UP_TIME)
            {
                lc_adc_detection.gauge_charge_cnt = 0;
                if(gb_user_system.bat_th_gauge < 98)
                {
                  gb_user_system.bat_th_gauge++;
                }
            }
      }
    }
//else if(gb_user_system.flag_f.adc_init_finish)                //adc初始化完成之后才判断低电量
    else
    {
      if(gauge < gb_user_system.bat_th_gauge)                //充电时电量只能增加,放电时电量只能减小 200108f
      {
            gb_user_system.bat_th_gauge = gauge;
      }
      //if(gauge <= BAT_GAUGE_LOW)
      if(gb_user_system.bat_th_gauge <= BAT_GAUGE_LOW)
      {
            if(lc_adc_detection.bat_low_cnt >= 5)
            {
                lc_adc_detection.flag_f.bat_low = 1;
            }
            else
            {
                lc_adc_detection.bat_low_cnt++;
            }
      }
      else
      {
            lc_adc_detection.bat_low_cnt = 0;
      }

      if(gb_user_system.bat_th_gauge <= BAT_GAUGE_NONE)
      {
            if(lc_adc_detection.bat_none_cnt >= 5)
            {
                lc_adc_detection.flag_f.bat_none = 1;
            }
            else
            {
                lc_adc_detection.bat_none_cnt++;
            }
      }
      else
      {
            lc_adc_detection.bat_none_cnt = 0;
      }
    }

    return gb_user_system.bat_th_gauge;
}
/******************************************************************************
** \brief    Bat Flag Get
**
** \param    null
**
** \return   flag
******************************************************************************/
_Bool BatLowFlagGet(void)
{
    return lc_adc_detection.flag_f.bat_low;
}

_Bool BatNoneFlagGet(void)
{
    return lc_adc_detection.flag_f.bat_none;
}


梁工 发表于 2023-3-3 22:51:23

980578873 发表于 2023-3-3 19:40
梁工,贴代码出来了,我没看懂,没办法模仿
/******************************************************** ...

缺乏解释的代码很难看懂的,我以为有原理说明。一般来说,仅仅测量电压是没法修正内阻造成的偏差的。
页: [1] 2
查看完整版本: 关于充放电电量指示灯不一致的问题