除了參數的監控與保護以外,做爲BMS系統,其中最重要的功能還有一項,那即是SOC的計算。算法
SOC,全稱是State of Charge,系統荷電狀態,也叫剩餘電量,表明的是電池使用一段時間或長期擱置不用後的剩餘容量與其徹底充電狀態的容量的比值,經常使用百分數表示。編程
其取值範圍爲0~100,當SOC=0時表示電池放電徹底,當SOC=100時表示電池徹底充滿。
網絡
那麼SOC有什麼意義呢?編程語言
任何一個產品,對於通常的終端用戶而言,若是對其直接提供電壓、電流之類的電池參數,那麼用戶可能十分費解,由於對他們來講,使用的電源產品惟一可以理解的,彷佛就只有電量。學習
好比當我詢問你的手機還剩多少電?你確定不會回答電池的端電壓還有3.54V,而是直接告訴我還剩大概80%。電動車也是同樣,咱們甚至能夠粗糙的用幾個柱狀圖來表示電池當前的情況,這樣也總比直接提供準確的電壓要好理解不少,即便不許,但能讓用戶直觀的理解工程師想要表達的意思。ui
所以,計算出準確的SOC,不只能提高用戶體驗,並且好能延長產品的使用壽命,這對於任何一塊產品而言意義都很是巨大。spa
在通常的BMS系統中,計算電量的方式大概有兩種:設計
①:硬件code
所謂硬件,即是使用一些專門的電量計芯片來計算電量,好比說TI的BQ34Z100,這是一塊基於阻抗跟蹤技術的電量計,其計算精度不錯,並且操做簡單,只須要在前期進行一些簡單的操做(使用TI官方軟件進行基本參數配置,計算電池化學參數CHEM_ID,進行充放電循環學習導出量產文件等)而後就能夠直接從芯片裏讀出電量值。(電量計芯片的方法本文不涉及,感興趣的同窗能夠自行查閱資料,或者在本文下留郵箱)orm
②:軟件
軟件計算SOC的方法也很多,有開路電壓法、安時積分法、內阻法、神經網絡和卡爾曼濾波法……
開路電壓法因爲要預計開路電壓,所以須要長時間靜置電池組,內阻法存在着估算內阻的困難,在硬件上也難以實現,神經網絡和卡爾曼濾波法則因爲系統設置的困難,並且在電池管理系統中應用時成本很高,不具有優點,所以相對於開路電壓法、內阻法、神經網絡和卡爾曼濾波法本而言,安時積分因爲簡單、有效而常被採用。
本文主要介紹基於STM32的BMS的SOC的編程語言算法。
----------------------------------------------------------------------------------------------
積分是一個數學模擬的概念,若是轉化爲生活語言,就是累積一端時間的量,若是轉化爲程序語言,就是把某個變量相乘在相加計算和。
安時積分中的基本參量天然是電流,在任何一個能源系統運行之時,最可以體現其運行負荷狀態的必然就是電流,好比一個電機,若是想要轉的快,迴路上的電流必然增大,好比一個燈泡,若是想要更亮更閃,迴路上的電流也要增大。
SOC的數學定義是什麼?
上面說過,SOC就是一顆電池還剩多少電,也就是容量,電池容量的定義是,在必定條件下,所放出的電量,即電池的容量,咱們一般以安培、小時做爲單位,簡稱安時(用 AH 表示)。
假若有一顆電池當前的容量是20AH,就是說明,若是咱們用1A的電流來進行放電,理論上它可使用20個小時,等咱們把這顆電池用光以後,再使用1A的電流來充電,理論上也須要20個小時才能充滿。
若是使用2A的電流來充放電,那麼時間也會從20小時縮短到10小時……
安時積分的基本原理就是把電流按照時間進行累計,而後記入剩餘電量之中,這和用管子朝泳池裏灌水是一個道理。
系統啓動,傳感器開始電流採集,假如每隔20us採集一次,若是採集到的充電電流是1.5A,那麼我就認爲,在這20us的時間段內,充電電流一直都是1.5A,那麼這段時間裏增長的電量就是1.5A * 20us(這裏爲了表達清晰暫時不轉換單位)。
系統充電:如今的剩餘電量 = 20us前的電量 + 20us內產生的電量;
系統放電:如今的剩餘電量 = 20us前的電量 - 20us內產生的電量;
咱們採集到的電流信息,是負載/充電器迴路上的電流信息,當電流大於某個閾值的時候(300mA),咱們認爲是充電,當電流小於某個閾值的時候(-300mA),咱們認爲是放電,這個沒有問題,不過,若是採集到的電流爲0,那麼系統就真的沒有任何消耗嗎?
固然不是,就算是MCU自己也是須要消耗能源的,它使用的也是電池的電,只不過沒有計入積分之中,若是想要算法更加精確,我認爲BMS板子的固定功耗是不能忽略的,雖然電流消耗不大,但畢竟是時時刻刻的在消耗,並且這個消耗幾乎不會有很大的改變,若是考慮了固定功耗,那麼新的算法以下:
系統充電:如今的剩餘電量 = 20us前的電量 + 20us內產生的電量 - 系統固定功耗電流;
系統放電:如今的剩餘電量 = 20us前的電量 - 20us內產生的電量 - 系統固定功耗電流;
tim_cal_ms = OSTimeGet() - time;//就算如今通過的時間 time = OSTimeGet(); //保存如今的時間 /* 安時積分 */ /* 容量 = (當前功率電流 - 系統固定功耗) * 時間 */ cap_mAms = (cap_mAms - (current_mA * tim_cal_ms)) - (SYSTEM_FIXED_POWER_CURRENT * tim_cal); /* 充電電量或者放電電量累積超過 10mAh */ if((cap_mAms > 36000000) || (cap_mAms < -36000000)) { capacity += cap_mAms / 3600000; /*整數個 mAh */ cap_mAms = cap_mAms % 3600000; /* 不足1mAh的電量,作累積 */ }
以上即是安時積分的基本原理,看着很是簡單,不過,如今還有一個問題,20us內產生的電量(current_mA * tim_cal_ms)這個值我是能夠計算出來的,可是20us前的電量(cap_mAms)這個值又從何而來呢?
這個天然是來自20us以前的狀態,咱們再往前推,找到40us前的狀態,而後再往前推,找到60us以前的狀態……
若是一直往前推之後,確定會發現一個問題,這個電量算法的安時積分須要一個起點,也就是系統運行以後,個人第一個參與計算的電量是多少?
初始電量的來源通常採用開路電壓法來確認,一顆新電池,若是在靜態的狀況下(無充放電)呆了2個小時以上,那麼這個時候直接使用電壓來尋找電量是很準確的,好比說3.6V對應100%電量,2.7V對應1%電量,3V對應50%電量,這徹底能夠作幾回充放電實驗來列出一個表,橫座標是電壓,縱座標就是電量。
1 const OCV_VALUE_S OcvTable_Dischg_1C[101] = { {2999, 0}, 2 {3129, 1}, {3262, 2}, {3292, 3}, {3314, 4}, {3330, 5}, 3 {3344, 6}, {3366, 7}, {3375, 8}, {3383, 9}, {3390, 10}, 4 {3404, 11}, {3410, 12}, {3416, 13}, {3421, 14}, {3426, 15}, 5 {3437, 16}, {3441, 17}, {3446, 18}, {3450, 19}, {3454, 20}, 6 {3462, 21}, {3466, 22}, {3470, 23}, {3473, 24}, {3480, 25}, 7 {3484, 26}, {3487, 27}, {3490, 28}, {3493, 29}, {3497, 30}, 8 {3503, 31}, {3506, 32}, {3510, 33}, {3513, 34}, {3519, 35}, 9 {3523, 36}, {3526, 37}, {3529, 38}, {3532, 39}, {3539, 40}, 10 {3543, 41}, {3546, 42}, {3550, 43}, {3554, 44}, {3561, 45}, 11 {3565, 46}, {3569, 47}, {3573, 48}, {3578, 49}, {3587, 50}, 12 {3592, 51}, {3596, 52}, {3601, 53}, {3607, 54}, {3617, 55}, 13 {3623, 56}, {3629, 57}, {3635, 58}, {3641, 59}, {3654, 60}, 14 {3661, 61}, {3667, 62}, {3674, 63}, {3688, 64}, {3696, 65}, 15 {3703, 66}, {3710, 67}, {3718, 68}, {3726, 69}, {3741, 70}, 16 {3749, 71}, {3758, 72}, {3766, 73}, {3782, 74}, {3790, 75}, 17 {3799, 76}, {3807, 77}, {3816, 78}, {3833, 79}, {3842, 80}, 18 {3851, 81}, {3860, 82}, {3877, 83}, {3887, 84}, {3896, 85}, 19 {3905, 86}, {3914, 87}, {3933, 88}, {3943, 89}, {3953, 90}, 20 {3962, 91}, {3972, 92}, {3992, 93}, {4002, 94}, {4013, 95}, 21 {4024, 96}, {4048, 97}, {4063, 98}, {4082, 99}, {4190, 100}};
系統上電之後,讀取電池總電壓,而後尋找到一個初始的電量值,雖然有些偏差,不過徹底能夠用這個值來參與之後的積分運算……
不過,還有一個問題,若是這塊電池並未靜置2小時以上,剛纔還在大功率放電,而後因爲某種問題系統重啓,這個時候採用開路電壓法彷佛就不可行了!
關於這個問題,能夠用一些設計來解決,好比MCU不斷電,或者設計一顆外部獨立RTC,增長外部的flash,實時儲存SOC和相關的信息,在系統啓動後,讀取flash中的SOC,而後判斷其存入的時刻是否通過了2小時,若是上一次存入的SOC的時間和如今的時間相差2小時,那麼能夠採用開路電壓法肯定初始SOC。若是事件間隔很小,那麼就直接使用FLASH中存儲的SOC值來當作初始SOC。
----------------------------------------------------------------------------------------------------------------
用安時積分算法來計算SOC,其最大的缺點就是偏差容易累積,甚至有些偏差是不可避免的,好比採用開路電壓法獲得的初始SOC值,好比硬件的採集精度,若是每次實際的充電電流是0.9A,而採集出來的電流是0.1A,按照時間久而久之的累積下去,最後的電量確定是虛高的。
在這個時候,咱們須要一些方法,利用電池自己的特性,來對安時積分算法進行校準。
鋰電池充電須要通過幾個過程,當電池電量低,那麼首先是恆流升壓充電,這時電流固定不變,電壓逐漸升高,等電池電量接近飽和之時,會變成恆壓降流充電,電壓不在發生明顯變化,電流會急速減少,過程以下圖所示。
正是由於鋰電池的如此特性,所以咱們便有了一個校準SOC的時機,當在充電過程當中,一旦出現了恆壓降流,而且這種狀態持續了足夠長的時間,那麼就能夠說明電池已經充滿了。
假如因爲以前的計算和採集存在偏差,致使系統如今的SOC等於70%,系統也能夠主動使用算法來修正這個結果,要麼直接將SOC人工設定爲100%,要麼主動放大積分的因子,使其加速充電,以更快的速度朝着100%畢竟。
在放電過程當中也能夠校準,當電池的電壓已經接近低壓極限,而後電流也只有幾百個毫安,那麼就能夠將SOC看作是0%了。
前面提到過,當電池靜置2小時之後,此刻電壓相對平穩,咱們可使用開路電壓法直接估算SOC的最新值,也能夠用開路電壓和卡爾曼濾波結合起來用。
卡爾曼濾波是一種頗有名的數據校準算法,也能夠應用在SOC的計算之上。
卡爾曼濾波的本質是解決一個信任度的問題,咱們採集到的電壓查表獲得的SOC,與安時積分計算出來的SOC,到底哪一個更加準確?
具體的算法理論這裏不展開,我直接把本身的代碼貼出來以供參考:
uint8_t Kalman_Filter_Algorithm(float calculation_vaule, float Q, float R, float measure_vaule) { static float x_last = 0; static float p_last = 0; static float kg; static float x_mid; static float x_now; static float p_mid; static float p_now; /* Q:過程噪聲,Q增大,動態響應變快,收斂穩定性變壞 R:測量噪聲,R增大,動態響應變慢,收斂穩定性變好 */ if ((measure_vaule > calculation_vaule) && ((measure_vaule - calculation_vaule) > 10)) { measure_vaule = calculation_vaule + 10; } else if ((calculation_vaule > measure_vaule) && ((calculation_vaule - measure_vaule) > 10)) { measure_vaule = calculation_vaule - 10; } else { ; } x_now = calculation_vaule; // 先驗估算值 x_mid = calculation_vaule; // 先驗協方差 p_mid = p_last + Q; // 卡爾曼增益 kg = p_mid / (p_mid + R); // 最優估計值 x_now = x_mid + kg*(measure_vaule - x_mid); // 最新協方差 p_now = (1 - kg)*p_mid; p_last = p_now; x_last = x_now; return (uint8_t)x_now; }
有了以上量準校準的方法,相信系統的SOC在通常狀況下不會誤差太大。
如今作個總結,關於SOC的計算策略圖以下: