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 等,啟到修正誤差的作用。