MTK平台充电调试总结

摘要:调试电池的充放电管理,首先需要深入了解锂电池的电池原理和特点,充放电特性以及基本的电池安全问题。然后需要对MTK的电池管理驱动程序有深入的了解,理解电池充放电算法的基本原理。在此基础上,对充放电导致的问题进行调试优化。

一、 锂电池工作原理和特性

1.工作原理:

 

锂离子电池以碳素材料为负极,以含锂的化合物作正极。它主要依靠锂离子在正极和负极之间移动来工作。在充放电过程中,Li+ 在两个电极之间往返嵌入和脱嵌:充电时,Li+从正极脱嵌,经过电解质嵌入负极,负极处于富锂状态;放电时则相反。

一般锂电池充电电流设定在0.2C至1C之间,电流越大,充电越快,同时电池发热也越大。而且,过大的电流充电,容量不够满,因为电池内部的电化学反应需要时间。就跟倒啤酒一样,倒太快的话会产生泡沫,反而不满。

2.主要性能:

锂离子电池的额定电压为3.7V(少数的是3.8V)。充满电时的终止充电电压与电池阳极材料有关,目前手机上主流的参数为4.35V。充电时要求终止充电电压的精度在±1%之内。锂离子电池的终止放电电压为2.4~2.7V(电池厂家给出工作电压范围或终止放电电压的参数略有不同)。高于终止充电电压及低于终止放电时会对电池有损害。其使用有一定要求:充电温度:0℃~45℃;保存温度:-20℃~+60℃。锂离子电池不适合大电流充放电。一般充电电流不大于1C,放电电流不大于2C(C是电池的容量,如C=950mAh,1C的充电率即充电电流为950mA)。充电、放电在20℃左右效果较好,在负温下不能充电,并且放电效果差(在-20℃放电效果最差,不仅放电电压低,放电时间比20℃放电时的一半还少)。

3.充放电特性

A.锂电池的充电特性: 

1.在充电前半段,电压是逐渐上升的; 

2.在电压达到4.2V后,内阻变化,电压维持不变;    

3.整个过程中,电量不断增加; 

4.在接近充满时,充电电流会达到很小的值。 

 经过多年的研究,已经找到了较好的充电控制方法:   

1.涓流充电达到放电终止电压2. 7V ;

2.使用恒流进行充电,使电压基本达到4.2V。安全电流为小于0.8C;

3.恒流阶段基本能达到电量的80%;

4.转为恒压充电,电流逐渐减小;

5.在电流达到较小的值(如0.05C)时,电池达到充满状态。 

这种CC-CV的充电方式能很好的到达电池的充满状态,并且不损害电池,已经成为锂离子电池的主要充电方式。 

但是在电池电压已经很低的情况下,电池内部的锂离子活性减弱,如果此时用比较大的电流充电,也有可能对电池有损害。如同人在剧烈运动前要进行必要的热身活动一样,锂离子的活性也要逐步激活。可以在电池低压段采用涓流方式,有效激活电池电压到2.7V以上,然后采用CC-CV的充电方式,有效的保护电池。

B.锂电池的放电特性; 

实验发现,锂离子电池在放电终止电压2.7V的条件下,放电电流越大电池的极化越大,电池的放电容量越小,但电池的静态电压与电池的放电深度的关系是基本保持不变的状态。锂离子电池以大电流放电(大于2C)的情况下,电池的放电曲线出现了电压先降低后上升的现象     通常情况下,确定锂离子电池放电电流大小时,不能用电流的绝对值来衡量,而用额定容量C与放电时间的比来表示,称作放电速率或放电倍率。对于1700mAh的电池,如果以0.1C的电流放电,则放电电流为170mA。由于锂离子电池的内阻,一般在30-100 mΩ之间,大电流放电或充电都会导致电池升温,因此在监测过程中,锂离子电池一般不允许高速率放电,一般放电速率应小于0.5C,最大连续放电速率不能超过1.5C,电压低于2.7V时应终止放电。

C.过充过放:

锂离子电池的额定电压,因为材料的变化,一般为3.7V,磷酸铁锂(以下称磷铁)正极的则为3.2V。充满电时的终止充电电压国际标准是4.2V,磷铁3.6V。锂离子电池的终止放电电压为2.75V~3.0V(国内电池厂给出工作电压范围或给出终止放电电压,各参数略有不同,一般为3.0~2.75V,磷铁为2.5V。)。低于2.5V(磷铁2.0V)继续放电称为过放(国际标准为最低3.2v,磷铁2.8v),低电压的过放或自放电反应会导致锂离子活性物质分解破坏,并不一定可以还原。而锂离子电池任何形式的过充都会导致电池性能受到严重破坏,甚至爆炸。锂离子电池在充电过程必需避免对电池产生过充。

二、 电池温度检测与充放电算法原理

1.电池温度检测

温度检测等效电路

l 将Vbat_on转化为NTC的电阻值;

l 利用线性插值法通过表格将NTC的电阻值转化为温度值。

2.充放电算法:

3.驱动工作流程:

A.电池管理框架

B.驱动工作流程

开始表演:

代码分析

Kernel:

kernel-3.10\drivers\power\mediatek\

kernel-3.10\drivers\misc\mediatek\mach\mt6580\<project_name>\power\

 上层通过读取创建一系列的设备节点获取电池相关的状态信息

android电源管理系统
/sys/class/power_supply/ac/online //AC 电源连接状态 交流电 即电源插座
/sys/class/power_supply/usb/online //USB电源连接状态
/sys/class/power_supply/battery/status //充电状态
/sys/class/power_supply/battery/health //电池状态
/sys/class/power_supply/battery/present //使用状态
/sys/class/power_supply/battery/capacity //电池 level
/sys/class/power_supply/battery/batt_vol //电池电压
/sys/class/power_supply/battery/batt_temp //电池温度
/sys/class/power_supply/battery/technology //电池技术

代码分解:

battery_common.c

在Battery驱动模块中,battery_probe函数中会创建一些设备节点,并且运行一个线程bat_thread_kthread获取电池相关的数据信息

battery_kthread_hrtimer_init();//检测电池插入/拔出
kthread_run(bat_thread_kthread, NULL, "bat_thread_kthread");

在bat_thread_kthread线程中

int bat_thread_kthread(void *x)
{
    ktime_t ktime = ktime_set(3, 0);    /* 10s, 10* 1000 ms */
 
#ifdef BATTERY_CDP_WORKAROUND
    if (is_usb_rdy() == KAL_FALSE) {
        battery_log(BAT_LOG_CRTI, "CDP, block\n");
        wait_event(bat_thread_wq, (is_usb_rdy() == KAL_TRUE));
        battery_log(BAT_LOG_CRTI, "CDP, free\n");
    } else{
        battery_log(BAT_LOG_CRTI, "CDP, PASS\n");
    }
#endif
 
    /* Run on a process content */
    while (1) {
        mutex_lock(&bat_mutex);
 
        if (((chargin_hw_init_done == KAL_TRUE) && (battery_suspended == KAL_FALSE)) || ((chargin_hw_init_done == KAL_TRUE) && (fg_wake_up_bat == KAL_TRUE)))
            BAT_thread();
 
        mutex_unlock(&bat_mutex);
 
#ifdef FG_BAT_INT
        if(fg_wake_up_bat==KAL_TRUE)
        {
            wake_unlock(&battery_fg_lock);
            fg_wake_up_bat=KAL_FALSE;
            battery_log(BAT_LOG_CRTI, "unlock battery_fg_lock \n");
        }
#endif //#ifdef FG_BAT_INT
 
        battery_log(BAT_LOG_FULL, "wait event\n");
 
        wait_event(bat_thread_wq, (bat_thread_timeout == KAL_TRUE));
 
        bat_thread_timeout = KAL_FALSE;
        hrtimer_start(&battery_kthread_timer, ktime, HRTIMER_MODE_REL);
        ktime = ktime_set(BAT_TASK_PERIOD, 0);    /* 10s, 10* 1000 ms 设置时间为10秒*/
        if (chr_wake_up_bat == KAL_TRUE && g_smartbook_update != 1)    /* for charger plug in/ out */
        {
            #if defined(CONFIG_MTK_DUAL_INPUT_CHARGER_SUPPORT)
            if (DISO_data.chr_get_diso_state) {
                DISO_data.chr_get_diso_state = KAL_FALSE;
                battery_charging_control(CHARGING_CMD_GET_DISO_STATE, &DISO_data);
            }
            #endif
 
            g_smartbook_update = 0;
            #if defined(CUST_CAPACITY_OCV2CV_TRANSFORM)
            battery_meter_set_reset_soc(KAL_FALSE);
            #endif
            battery_meter_reset();
            chr_wake_up_bat = KAL_FALSE;
 
            battery_log(BAT_LOG_CRTI,
                        "[BATTERY] Charger plug in/out, Call battery_meter_reset. (%d)\n",
                        BMT_status.UI_SOC);
        }
 
    }
 
    return 0;

在这个线程中,每隔10s会去调用函数BAT_Thread去获取电池数据

BAT_Thread

void BAT_thread(void)
{
    static kal_bool battery_meter_initilized = KAL_FALSE;
    if (battery_meter_initilized == KAL_FALSE) {
        battery_meter_initial();    /* move from battery_probe() to decrease booting time 第一次进该函数会进行一些初始化,如设置D0的值(初始电量,构建电池曲线等) table_init, oam_init*/
        BMT_status.nPercent_ZCV = battery_meter_get_battery_nPercent_zcv();
        battery_meter_initilized = KAL_TRUE;
    }
 
    mt_battery_charger_detect_check();//充电器型号,是否插入等方面的检测,
    mt_battery_GetBatteryData();//核心函数获取电池数据
    if (BMT_status.charger_exist == KAL_TRUE) {
        check_battery_exist();
    }
    mt_battery_thermal_check();//电池温度检测以及开机mode
    mt_battery_notify_check();//检测电池电压,电流等
 
    if (BMT_status.charger_exist == KAL_TRUE) {
        mt_battery_CheckBatteryStatus();//充电异常检测
        mt_battery_charging_algorithm();//切换充电模式 Pre_CC->CC->CV->Full
    }
 
    mt_battery_update_status();//上报电池数据
    mt_kpoc_power_off_check();
}

mt_battery_GetBatteryData:

void mt_battery_GetBatteryData(void)
{
    kal_uint32 bat_vol, charger_vol, Vsense, ZCV;
    kal_int32 ICharging, temperature, temperatureR, temperatureV, SOC;
    static kal_int32 bat_sum, icharging_sum, temperature_sum;
    static kal_int32 batteryVoltageBuffer[BATTERY_AVERAGE_SIZE];
    static kal_int32 batteryCurrentBuffer[BATTERY_AVERAGE_SIZE];
    static kal_int32 batteryTempBuffer[BATTERY_AVERAGE_SIZE];
    static kal_uint8 batteryIndex = 0;
    static kal_int32 previous_SOC = -1;
 
    bat_vol = battery_meter_get_battery_voltage(KAL_TRUE);
    Vsense = battery_meter_get_VSense();
    if( upmu_is_chr_det() == KAL_TRUE ) {
    ICharging = battery_meter_get_charging_current();
    } else {
        ICharging = 0;
    }
 
    charger_vol = battery_meter_get_charger_voltage();
    temperature = battery_meter_get_battery_temperature();
    temperatureV = battery_meter_get_tempV();
    temperatureR = battery_meter_get_tempR(temperatureV);
 
    if (bat_meter_timeout == KAL_TRUE || bat_spm_timeout == TRUE || fg_wake_up_bat== KAL_TRUE) 
    {
        SOC = battery_meter_get_battery_percentage();//获取电池电量
        //if (bat_spm_timeout == true)
            //BMT_status.UI_SOC = battery_meter_get_battery_percentage();
 
        bat_meter_timeout = KAL_FALSE;
        bat_spm_timeout = FALSE;
    } else {
        if (previous_SOC == -1)
            SOC = battery_meter_get_battery_percentage();
        else
            SOC = previous_SOC;
    }
 
    ZCV = battery_meter_get_battery_zcv();
 
    BMT_status.ICharging =
        mt_battery_average_method(BATTERY_AVG_CURRENT, &batteryCurrentBuffer[0], ICharging, &icharging_sum,
                      batteryIndex);
 
    
    if (previous_SOC == -1 && bat_vol <= batt_cust_data.v_0percent_tracking) {
        battery_log(BAT_LOG_CRTI,
                    "battery voltage too low, use ZCV to init average data.\n");
        BMT_status.bat_vol =
            mt_battery_average_method(BATTERY_AVG_VOLT, &batteryVoltageBuffer[0], ZCV, &bat_sum,
                          batteryIndex);
    } else {
        BMT_status.bat_vol =
            mt_battery_average_method(BATTERY_AVG_VOLT, &batteryVoltageBuffer[0], bat_vol, &bat_sum,
                          batteryIndex);
    }
 
 
    if (battery_cmd_thermal_test_mode == 1)
    {
        battery_log(BAT_LOG_CRTI,
                    "test mode , battery temperature is fixed.\n");    
    }
    else
    {
    BMT_status.temperature =
        mt_battery_average_method(BATTERY_AVG_TEMP, &batteryTempBuffer[0], temperature, &temperature_sum,
                      batteryIndex);
    }
 
 
    BMT_status.Vsense = Vsense;
    BMT_status.charger_vol = charger_vol;
    BMT_status.temperatureV = temperatureV;
    BMT_status.temperatureR = temperatureR;
    BMT_status.SOC = SOC;
    BMT_status.ZCV = ZCV;
 
#if !defined(CUST_CAPACITY_OCV2CV_TRANSFORM)
    if (BMT_status.charger_exist == KAL_FALSE) {
        if (BMT_status.SOC > previous_SOC && previous_SOC >= 0)
            BMT_status.SOC = previous_SOC;
    }
#endif
 
    previous_SOC = BMT_status.SOC;
 
    batteryIndex++;
    if (batteryIndex >= BATTERY_AVERAGE_SIZE)
        batteryIndex = 0;
 
 
    if (g_battery_soc_ready == KAL_FALSE)
        g_battery_soc_ready = KAL_TRUE;
 
    battery_log(BAT_LOG_CRTI,
                "AvgVbat=(%d),bat_vol=(%d),AvgI=(%d),I=(%d),VChr=(%d),AvgT=(%d),T=(%d),pre_SOC=(%d),SOC=(%d),ZCV=(%d)\n",
                BMT_status.bat_vol, bat_vol, BMT_status.ICharging, ICharging,
                BMT_status.charger_vol, BMT_status.temperature, temperature,
                previous_SOC, BMT_status.SOC, BMT_status.ZCV);
 
 
}

battery_meter_get_battery_percentage:

kal_int32 battery_meter_get_battery_percentage(void)
{
//去掉了一些无效代码
    if (bat_is_charger_exist() == KAL_FALSE)
        fg_qmax_update_for_aging_flag = 1;
 
 
    oam_run();//核心函数
 
    return (100 - oam_d_5);
 
}

oam_run:

void oam_run(void)
{
    int vol_bat = 0;
    /* int vol_bat_hw_ocv=0; */
    /* int d_hw_ocv=0; */
    int charging_current = 0;
    int ret = 0;
    /* kal_uint32 now_time; */
    struct timespec now_time;
    kal_int32 delta_time = 0;
 
    /* now_time = rtc_read_hw_time(); */
    get_monotonic_boottime(&now_time);    
 
    /* delta_time = now_time - last_oam_run_time; */
    delta_time = now_time.tv_sec - last_oam_run_time.tv_sec;
 
 
    last_oam_run_time = now_time;
 
    /* Reconstruct table if temp changed; */
    fgauge_construct_table_by_temp();
 
 
    vol_bat = 15;        /* set avg times */
    ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat);//首先获取电池Vbat端的电压
 
    oam_i_1 = (((oam_v_ocv_1 - vol_bat) * 1000) * 10) / oam_r_1;    /* 0.1mA  计算电流oam_v_ocv_1为上一次测得的开路电压*/
    oam_i_2 = (((oam_v_ocv_2 - vol_bat) * 1000) * 10) / oam_r_2;    /* 0.1mA */
 
    oam_car_1 = (oam_i_1 * delta_time / 3600) + oam_car_1;    /* 0.1mAh 损失/获取 的电量 = i*t 即为放电深度,oam_car_1>0则为放电,反之则为充电*/
    oam_car_2 = (oam_i_2 * delta_time / 3600) + oam_car_2;    /* 0.1mAh */
 
    oam_d_1 = oam_d0 + (oam_car_1 * 100 / 10) / gFG_BATT_CAPACITY_aging;//<span style="font-family: Arial, Helvetica, sans-serif;">gFG_BATT_CAPACITY_aging为电池总的容量,在oam_init的时候赋值</span>
 
    if (oam_d_1 < 0)
        oam_d_1 = 0;
    if (oam_d_1 > 100)
        oam_d_1 = 100;
 
    oam_d_2 = oam_d0 + (oam_car_2 * 100 / 10) / gFG_BATT_CAPACITY_aging;
    if (oam_d_2 < 0)
        oam_d_2 = 0;
    if (oam_d_2 > 100)
        oam_d_2 = 100;
 
    oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);
 
    oam_d_3 = fgauge_read_d_by_v(oam_v_ocv_1);//算出的开路电压查表获得放电深度
    if (oam_d_3 < 0)
        oam_d_3 = 0;
    if (oam_d_3 > 100)
        oam_d_3 = 100;
 
    oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);
 
    oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);
    oam_r_2 = fgauge_read_r_bat_by_v(oam_v_ocv_2);
 
 
    oam_d_4 = oam_d_3;
 
    gFG_columb = oam_car_2 / 10;    /* mAh */
 
    if ((oam_i_1 < 0) || (oam_i_2 < 0))
        gFG_Is_Charging = KAL_TRUE;
    else
        gFG_Is_Charging = KAL_FALSE;
 
 
    d5_count_time = 60;
 
    d5_count = d5_count + delta_time;
    if (d5_count >= d5_count_time) {
        if (gFG_Is_Charging == KAL_FALSE) {
            if (oam_d_3 > oam_d_5) {//在放电状态下,oam_d_3大于上一次电量oam_d_5,则电池电量-1
                oam_d_5 = oam_d_5 + 1;
            } else {
                if (oam_d_4 > oam_d_5) {
                    oam_d_5 = oam_d_5 + 1;
                }
            }
        } else {
            if (oam_d_5 > oam_d_3) {//<span style="font-family: Arial, Helvetica, sans-serif;">在充电状态下,oam_d_3小于上一次电量oam_d_5,则电池电量+1</span>
 
                oam_d_5 = oam_d_5 - 1;
            } else {
                if (oam_d_4 < oam_d_5) {
                    oam_d_5 = oam_d_5 - 1;
                }
            }
        }
        d5_count = 0;
        oam_d_3_pre = oam_d_3;
        oam_d_4_pre = oam_d_4;
    }
 
}

oam_run的整个的一个流程如下图所示:

MTK采用的Fuel方案获取剩余电池电量

首先通过adc读取电池 Vbat脚的电压值,然后通过这个闭路电压值查找 R—Table (cust_battery_meter_table.h )获得当前电压和温度下的电池内阻值,然后通过递归回溯的方法得到开路电压 OCV,然后通过这个OCV电压值查找 放电深度表获取当前的放电深度,从而算出剩余的电池电量。

调试驱动时应注意的一些关键点

1、电池曲线

2、充电电流的一些设置(AC_CHARGER_CURRENT,NON_STD_AC_CHARGER_CURRENT,USB_CHARGER_CURRENT等),是否是高压电池HIGH_BATTERY_VOLTAGE_SUPPORT

最高温度: MAX_CHARGE_TEMPERATURE
最高电压: V_CHARGER_MAX

截止满充电流大小:CHARGING_FULL_CURRENT

充关机最大电池差异:CUST_POWERON_DELTA_CAPACITY_TOLRANCE(若小于这个值,则下一次开机用RTC里面存储的剩余电池电量值)

关机电压:SYSTEM_OFF_VOLTAGE

UI电量同步时间:SYNC_TO_REAL_TRACKING_TIME

=======================================================================

mt6735/mt8735硬件电量计执行流程

mt6735/mt8735 目前默认使用硬件电量计(SOC_BY_HW_FG)采集电池电量,现在简单描述一下这硬件电量计的工作过程。

1. 首先,系统在kernel-3.10/drivers/misc/mediatek/mach/mt6735/ap7350_tb_l1/power/cust_battery_meter.h 头文件中默认定义了 #define SOC_BY_HW_FG,使用硬件电量计的算法
文件位置kernel-3.10/drivers/power/mediatek/battery_meter.c

2. 初始化电量计
函数 fgauge_initialization 和 fgauge_algo_run_init

3. 在fgauge_construct_battery_profile_init里面根据cust_battery_meter_table.h里面的信息重构zcv表格,分50个等级,每2%一级。

4. 读取电池温度,电池容量等信息,并根据当前温度重构电池内阻表和zcv表。

5. 读取闭路电压,并用函fgauge_compensate_battery_voltage_recursion计算出开路电压,主要是做了一个电压补偿,之后用开路电压查找zcv表计算电池剩余容量。

6. 调用函数battery_meter_ctrl(BATTERY_METER_CMD_GET_HW_OCV,&gFG_voltage),直接从PMIC读取电池的开路电压,并计算电池剩余容量。

7. 读取rtc里面保存的电量值和从4和5里面计算出来的电量值做对比判断,选择合适的电量值作为电池的初始电量。
   文件位置kernel-3.10/drivers/power/mediatek/battery_common.c

(1)函数mt_battery_charger_detect_check完成充电器是否插入的判断。

(2)函数mt_battery_GetBatteryData主要完成电池信息的获取,获取电池电压,电流和温度等信息。过程中会调用函数fgauge_algo_run,此函数输出的log信息对调试电池电量不准的问题有很大的帮助。
mt_battery_GetBatteryData()读取电池相关信息:电压、电流、充电器电压、电池温度,通过SOC = battery_meter_get_battery_percentage();来获取电池电量百分比。里面会调fgauge_algo_run()这个函数来循环获取电量值,原理上与dod0值的获取类似。
当机器用的是硬件电量计的时候,zcv计算的电量实际上在这里只是起到参考作用,并不会影响系统的电量显示和使用。如果软件zcv表格是正确的,但这里的zcv计算电量和电量计计算电量相差比较大,这时候就要考虑调节电池容量的大小,一般在放电过程中zcv计算电量比电量计计算电量大,就要适当的增加电池容量,反之则减少。电池容量设的太小,会出现电量显示1%时还能用很久的现象,太大则会出现低电量时电量下降的很快,或还没到0%就关机了。

(3)函数mt_battery_thermal_check则实现温度的监控,当电池问题高于设定温度,一般是60度时系统会强制关机。

(4)函数mt_battery_notify_check则实现温度、电压和电量等一些异常并通知android层,最终提示给用户。

(5)当有充电器插入时,系统会运行mt_battery_CheckBatteryStatus检查电池的一些状态,从而决定充电与否,里面包含了高低温,充电器电压,是否在通话状态和充电时间等检测。

(6)函数mt_battery_charging_algorithm完成充电算法。这里两种充电方式,一是PMIC充电的Liner_charging和外部充电IC的Switch_charging。一般来说Switch_charging的充电电流要比Liner_charging大。mt_battery_charging_algorithm()完成充电算法:PMIC充电的Liner_charging和外部充电IC的Switch_charging。两种充电方式的主流程其实区别不大,基本都包含了预充电,恒流充电,满电判断和二次充电。有时候会出现充满电时,电池电压偏低的情况,这时候可以适当提高充电电压或者减少充电截止电流。

(7)函数mt_battery_update_status主要实现将电池,充电等状态更新到android层,如电量,充电器是否插入等等。当电量比较低的时候battery_update里面会调用到mt_battery_0Percent_tracking_check 电池电压小于SYSTEM_OFF_VOLTAGE时,BMT_status.UI_SOC减少到0,这时android上层会接收到电量等于0的更新,并发送命令关闭机器。

(8)mt_kpoc_power_off_check()实现关机充电时对charger的检测
 

====================================================================

一、MTK电池电量算法

1、SOC_BY_AUXADC:通过模数转换硬件获得电池电压,由电压来换算电量,误差大; 
2、SOC_BY_SW_FG:通过库伦积分算法获得电量,其中电流由软件计算获得(MTK平台常用的电池电量算法); 
3、SOC_BY_HW_FG:通过库仑积分算法获得电量,其中电流由硬件的模数转换获得; 
4、SOC_BY_SW_FG与SOC_BY_HW_FG的区别在于放电电量的获取方式,一个由软件计算获得,一个由硬件模数转换获得。

 

  • 3
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值