1. 程式人生 > >MTK Fuel Gauge演算法分析

MTK Fuel Gauge演算法分析

Battery 架構簡析

MTK 平臺 Battery 軟體架構基本如上圖所示。

具體過程:

硬體 ADC 讀取 Battery 的各路資訊:包括溫度,電壓等。
MTK 開發的電量演算法分析得到的資料。
Kernel 層將電量資訊通過寫檔案節點的方式更新,並通過 UEVENT 通知上層。
上層 Service 開啟 UEVENT LISTENER,監聽到 UEVENT 後,讀取 battery 相關檔案節點, 獲取電量資訊。
Service 更新資料後,通過 Broadcast 通知所有開啟了相關 listener 的 activities。

根據不同的電量讀取和計算的策略,第一步的讀取和第二步的演算法部分會有比較大的差異, 而後面的資料更新和事件通知部分一致性較高。

本篇重點分析 72/82 平臺 SW FG 演算法實現,對比 SW_FG 和 HW_FG 在硬體及軟體上的部 分差異,分析電量誤差形成的一些原因和 MTK 已經採取的消除誤差的措施。對於 Battery 資料更新和充電流程則粗略分析。

充電狀態機,battery 充電的邏輯,就依賴於這張圖,如果是用的 external charger ic,則應當 參考該 IC 的充電邏輯。

linear charging 下 cc 轉 cv,是通過 ADC 讀取電壓後,軟體切換。而使用 charger ic 則很 可能是硬體直接切換。

這部分的相關程式碼路徑在:
alps/mediatek/kernel/drivers/power/linear_charging.c
alps/mediatek/kernel/drivers/power/switching_charging.c

kernel 層 battery 驅動工作的流程,Bat_thread 是工作的重點,通過單獨的執行緒依賴 10s 定時 器,更新 battery 相關資訊。電量演算法分析後得到的資料也不會直接 update,Information Processing 還會針對一些特殊情況對顯示電量做調整,比如 0%tracking&100%tracking。

除了 10s 一次的定時器更新,插拔充電器會觸發中斷,中斷處理時同樣會更新 battery 資料。

所有和 電池 充電相關的資料都儲存在 power_supply 型別的結構體中,這是 linux 標準的電 源子系統體系。

MTK 電量演算法簡析

為了得到較為精確的電量資料,需要改善測量方式和計算方法,並針對已知誤差採取優化手段。一下介紹 MTK 平臺下采用的一些電量演算法。

AUX ADC 演算法:

事實上,所有演算法都要依賴ADC讀取電量資訊,這邊的AUX ADC演算法指只依賴ADC讀 值 然後查表讀取電量的演算法。

這種演算法只重構了 ZCV table,誤差會很大。

庫侖積分法:

通過開路電壓查表得到初始電量 D0,後續電量通過電流積分累積,通用性強,依賴初始電量的精確度。

混合型演算法: SW FG 演算法 HW FG 演算法。事實上 MTK 平臺專案通常採用的是混合型演算法。

SW FG 的參考電路:

HW FG 的參考電路:

相同點:NTC 電阻用於測量溫度 ADC 測量各路訊號
不同點:HW FG 有單獨的 ADC 和 20 毫歐的電阻 作電流的偵測。
HW FG 和 SW FG 最大差異就是電流的獲取方式。

混合演算法的流程,HW FG 通過 FGADC 讀取 FG 電阻兩端電壓獲得電流, 而 SW FG 則結 合庫倫演算法通過 SW 方式算得。這部分會詳細介紹。

72/82 平臺 SW FG 演算法分析

主要分析上圖黃色部分

大部分專案都採用混合演算法,下面從演算法初始化開始介紹下 SW FG 的演算法實現。

battery_meter.c 這個 C 檔案 主要負責電池電量演算法的實現 向上主要承接 battery_common.c 向下呼叫
battery_meter_hal.c 中的介面,以讀取電池的各路訊號。

=>battery_meter_initial

首先看下呼叫這個 func 的 timing。

顯然 在開機初始化階段,就會進入該函式,且只會執行一次。

針對 AUXADC SW_FG HW_FG 三種不同的電池演算法方案分別初始化,因為 82 平臺採 用的 SW_FG, 所以接下去先主要分析 SW_FG 的流程。

SW_FG 的準備工作 分為兩步: table_init oam_init

先看 table_init

首先要獲取當前的溫度資訊

=> force_get_tbat

ADC 讀值

這邊就是 MTK 為了結合實際溫度 獲取較為精確的電池資訊 而採取的線性平均值法。原理是利用預先測得的分佈在-10 0 25 50 攝氏度下的 ZCV 表,結合真實溫度,動態重構一張當 前溫度下的 ZCV 表格。

TEMPERATURE 對應預留的空 ZCV 表格,如下

構造新表的函式如下

採用線性平均法 填補了有效溫度內所有的 ZCV 對應值 但與真實曲線必然存在一定的誤差。

=>oam_init

常見的指標函式傳參比較有趣,vol_bat 這個引數下傳給底下 pmic 做 count,然後被重新賦值成讀取的v_bat值 之所以能這樣做是因為這兩塊程式碼同處在kernel層並地址傳參

battery_meter_hal.c 雖然頂著 hal 的名頭,其實是驅動程式,工作在核心層,主要實現上表 各結構體 針對 MTK 不同種的充電方案 讀取各項引數,包括 v_bat temperature v_i_sense 等

這邊走 pmic

這個函式也是起分流作用的 通過dwchannel,分到不同的處理函式去。硬體上,ADC通過 一個 mux 資料選擇器 對各路模擬訊號進行切換 有點類似 cpu 的時間片和行動通訊的時 隙切換。

vbat 是 channel 5, 要等到 adc 資料 ready才能去讀暫存器,看一下 pmic 的手冊

精度 15bit 的 ADC 其中 14bit 用來儲存資料 1 個 bit 做 ready 訊號 ,似乎 ADC3 和我們之前的 dwchannel number 有點對不上?

可以看到dwchannel 5 最終訪問的仍是 ADC3,另外可以直接比較下暫存器地址。

和 datasheet 左上角的暫存器地址一致

最後還要做次數值轉換,公式如下:

解析度計算:測量電壓範圍/(2^AD 位數-1)

另外,對於同為電壓值的 v_bat 和 v_i_sense,可能會出現 adc 量程不夠的問題 這時候需 要通過電阻分壓。 所以 case 6 和 7 的 r_val_temp 為分壓比

和前面一樣 ADC 讀值 但是 6320 的 spec 上對於 PCHR 沒有說明 從函式定義的名稱上看 是開路電壓但是如何在一個閉路的環境中通過 ADC 讀取 ocv,有些不解。 查了下讀這 個值的 timing,只有在 init suspend 和 resume 的時候才去獲取.猜想 一是可能 利用 linear charging 這種充 9 停 1 的方式,在第 10s 讀取電壓 作為 OCV 也可能是因為剛開機時 電流 還不大 讀到的電壓值 可用作 OCV 總之 這個值應該是真實的開路電壓的一個近似值。

=> oam_init

根據電壓讀表獲取電量

可以看到用的是table_init時重構的新表

Ok 這邊再一次 利用線性平均法 這一次是針對 ADC 讀到測到的電壓 線性平均後 得 到一個較精準的電量

=> oam_init

首先判斷是否插著充電器,讀 PMU 暫存器實現

為什麼 需要判斷有沒有插著充電器呢?

我是這麼理解的,之前通過 ADC 讀取了兩個電壓值 其中一個是 V_BAT,另一個是 hw_ocv 是在閉路環境下讀取的開路電壓近似值, 如果此時插著充電器,會有充電電流通過 這個 hw_ocv 的值和開路電壓的誤差會增大,因此需要做進一步的處理。

插入充電器時,電量誤差不大於 30,滿電誤差不大於 10,否則 hw_ocv 無效

=>oam_init

Dod 是指用電深度,100-dod = 電池剩餘容量

看一下 dod_init 的實現

dod init

首先看 g_rtc_fg_soc 這個變數,MTK 的策略是每隔一段時間將當前電量儲存到 RTC 暫存器中,在開機時讀取該電量。主要目的是改善使用者體驗。

看下這個值是何時被寫入的:

電池資訊 10s update 1 次同樣 UI 電量 10s 存入 RTC 暫存器一次。

用7個bit 儲存電量資訊。

開機時直接顯示關機時儲存的電量,會增強使用者體驗性,但是如果是更換電池或其他情況造成關機電量和開機電量相差過大,顯然應該採用開機電量,否則後續電池電量跳變反而會影 響使用者體驗。

Normal boot 下忽略||後面的條件主要就是要求沒有插入充電器不處於低電量誤差不超過 40%

經過前面所有的判斷最終得到了 gFG_capacity 這個電量,也就是開機電量,因為開機電量在整個電量計算中相當重要並且又要結合使用者體驗 所以之前會有很多的條件分支。

Q_MAX_POS_25 是常溫下電池容量因為 FG 演算法計算電池容量會用到庫倫積分所以需要關注電池容量的問題。這個值需要根據實際電池容量客製化。

即 25 度用標準容量其他溫度下需要乘上一個比例值。

oam_v_ocv_1 和oam_v_ocv_2 現在是根據 dod_init的結果取得的 大部分情況下就是關機電量查表得到的ocv電壓值 而註釋掉的原方案直接採用hw_ocv的值

這邊是算 ocv1 和 ocv2 對應的電池內阻 r,通過查表的方式獲取,因為 r 和 v 的對應表也是開路條件下測得所以用 hw_ocv 查表獲取的值比原先通過 vbat 取平均要精準些。

其他一些 oam 電量演算法 需要的引數初始化

oam_init 後, oam_run 這個 func 負責 電量的計算,看一下呼叫的時機。

=>mt_battery_GetBatteryData

顯然也是 10s 輪詢一次,get_percentage 這個 func 多個分支對應不同的電量演算法

=>oam_run

先看下 MTK SW FG 演算法的原理圖

SW FG 的核心 在於 通過兩種方式更新電壓,去逼近真實開路電壓 最終查表獲取近似真 實的電量值。

ocv1 被假定為開路電壓 ocv2 則是閉路電壓,以下結合實際程式碼和上述流程圖分析下 SW_FG 演算法流程

D0 D1 D2 D3 D4 D5 代表不同的放電深度

這個演算法的思路是這樣的: 最終通過開路電壓 oam_v_ocv_1 查 ZCV 表得到當前的電量值
-> 開路電壓需要通過閉路電壓 v_bat 和 閉路電流 oam_i_2 去回溯電池內阻 逐次逼近 –> oam_i_2 通過 另一種方式 電量積分更新的電壓 oam_v_ocv_2

總的來說:電壓通過兩種方式更新 電流積分求電量後查表 /電池內阻回溯 IR drop 求得
電池內阻 更新方式只有一種 根據電壓查表

具體分析部分程式碼:

閉路電壓的更新不需要演算法支援直接通過讀暫存器實現,注意vol_bat這個引數被複用, 下傳表平均次數 返回時為最終的v_bat電壓值

ocv_1 和 ocv_2 分別是兩種方式更新的電壓,這邊通過內阻的 IR drop 求電流.

上圖 R 可以是電池內阻

關鍵是 oam_i_2 這邊的 I2 有幾個作用:

<1>因為電流是通過上圖的內阻IR drop得到的,而方式一內阻回溯逼近開路電壓本質也是 IR drop,如果使用 oam_i_1 則沒有意義,只能使用不同體系的 I2.
<2>方式二 電流積分求電量查表 同樣依賴 oam_i_2 這個體系是累積積分 不需要引用其 他體系的引數
<3>I2 的方向作為充電還是放電的依據

而 oam_i_1 只有作用 3

oam_car_1/oam_car_2 是累積電量 顯然 oam_car_2 是演算法的有效引數

gFG_BATT_CAPACITY_aging 是電池總容量,之前分析過了,根據開機時讀取的電池溫度會有所不同。

d2 為積分法算出的電池當前容量;d0 為開機電量,不會更新;d1 不重要

內阻回溯 IR drop 逼近開路電壓,具體分析下:

主要是這個 for 迴圈,首先通過 v_bat 去查表得到電池內阻 r 然後用另一種演算法求得的電流 I 和內阻 r 算出內阻分去的電壓 v,推算 ocv_1.

幾次迴圈 反覆這個過程 逼近真實的開路電壓。

有幾個注意點:
1.

這邊的電壓單位到底是 v 還是 mv? 實際上是 0.1mv

2.gFG_resistance_bat 和 R_FG_VALUE 分別是指代 電池內阻和硬體 FG 使用的 FG 電阻(一般是 20 毫歐) 這邊是 SW_FG 所以後一項為 0

3.

因為 ret_compensate 是 int 型變數 做除法時取整處理 會引入較大誤差, 加上這個值使結果四捨五入

實際做 6 次內阻回溯,這時的電壓值已經和開路電壓比較接近,查表得到 D3,D3 基本就能反映當前電量了。

MTK 在 D3 的問題上針對電量跳變的情況 又做了步優化得到 D5,看下程式碼

這部分程式碼比較簡單,1 分鐘內電量值不會改變,且每分鐘電量的變化不會大於 1%,這樣 使用者體驗會比較好。防止因為低電壓時陡峭的電量曲線,以及比較耗電的應用,或突然的大 電流引起的電量跳變。當然特殊應用下應該取消這個功能,否則會帶來電量變化延時等問題。

這邊的返回值將填充 BMT_status.SOC ,這個引數再經過優化得到 BMT_status.UI_SOC 就是選單欄看到的電池電量了。

誤差和消除誤差

因為電量值本身不容易通過儀器直接測量,只能依賴開路電壓與電量的關係即電量曲線或者 電流積分公式演算,這樣一個過程會有很多產生誤差的點,MTK 針對其中一些給出了優化 方式。

另外,由於電池特性,有些時候真實的電量資料反而使得使用者體驗下降,這時候還要針對電量 資料做一些特殊處理,以滿足使用者的預期。

以下是 82 平臺 SW FG 中部分電量誤差產生的原因 以及 MTK 用於消除/減小誤差的方法。

庫倫積分時的電流:

即使是 cc 狀態,電流也是有波動的,而進入 cv 狀態後,電流的變化會更大。因為這樣一個 電流變化並無規律,所以不可能匯出電流公式用於電量積分。目前程式碼會把每 10s 算一次 電流作為這 10s 的平均電流。

這樣會形成一個電量累積誤差。

MTK 的觀點: MTK 認為電流誤差既有正誤差,也有負誤差,在較長的一段時間內認為誤差 相互抵消。但實際進入恆壓後,誤差應該會稍大。

電池內阻不穩定造成的誤差:

電池的內阻會隨溫度變化,且幅度很大,而在 SW FG 的演算法中依賴電池內阻計算電流大小, 如果使用固定值的電池內阻會嚴重影響電流的計算。

MTK 應對方案:

1.建立 zcv table

MTK 的 zcv table,建立了幾個特定溫度(-10,0,25,50)下的內阻 r 和開路電壓 ocv 的關 系,這樣可以根據可量測的電壓訊號查表推算內阻

2.線性平均值

MTK 在演算法初始化時,讀取溫度資訊,通過線性平均重構 zcv table,這樣可以覆蓋從-10 到 50 的所有溫度點。

82 平臺 MTK 電量演算法只會在初始化的時候去通過讀取的溫度重構 zcv table,但假設使用 者周圍的溫度在手機使用過程中變化比較大,或者電池本身發熱很厲害,電池內阻值有了較 大改變,則測出的電量偏差也會比較大。

假設從高溫環境到低溫環境,根據電池特性,電池內阻會大幅增大,而電池可使用容量將會下降。實際耗流假設變化不大,由於還是用之前的 zcv 表格,用於計算的內阻比實際內阻小很多,則算出來的電流會偏大。這樣顯示電量會快速下降。

開機電壓的誤差:

由於開機過程中,外設逐漸進入工作狀態,電流逐漸增大,這時候 ADC 讀到的電壓偏差比較大。

MTK方案:89的HW FG會在PMU未開放restb時 獲取電壓 取代82的sw讀開機ocv的方案

ADC 精度問題

這是硬體設施的問題,有一點,精度和解析度是兩碼事。精度指轉換後所得結果相對與實際值的準確度 、解析度是指轉換器所能分辨的模擬訊號的最小變化值。

插拔和重新開機的顯示誤差

開機過程中有累積誤差,開機讀取 ocv 查表同樣有誤差,這樣的結果是使用者可能看到開關機的電量出現很大誤差。

MTK 方案:使用 rtc 暫存器每隔一段時間儲存當前電量,開機時若開機電量和關機差別不大, 則直接使用關機前電量,以改善使用者體驗。

電池老化的誤差

電池在使用較長時間後,容量會減小。

MTK 方案: HW FG 可以通過充電到 100%的過程重新算得電池最大容量,SW FG 則並沒有採取這種方 案,可能是 SW FG 的庫倫積分誤差較大。

100%tracking & 0%tracking

充滿電和關機實際上有兩個判斷標準:

軟體關機電壓:system_off_voltage 電量顯示 0%
截止電流:top_off_current 電量顯示 100%

由於誤差,這兩套標準在實際使用中肯定不一致,MTK 通過 UI_SOC 這個變數對演算法得到 SOC 進行處理。讓電量顯示 follow 電壓和電流的判斷。

UI_SOC 如先到 0%,需要等待電壓和電流的判斷;
UI_SOC 如後到,則需加快步伐 每次-1

程式碼如下:

100% tracking 原理和上面的差不多,不一一列舉。

充滿電後,還回去 reset 之前 FG 演算法的一些引數 比如 DOD CAR 等,啟到修正誤差的作用。