本文轉載自:http://blog.csdn.net/xubin341719/article/details/8969369
電池電量計,庫侖計,用max17040這顆電量IC去計量電池電量,這種方法比較合理。想起比較遙遠的年代,做samsung s5pc110/sp5v210的時候,計量電量用一個AD口加兩個分壓電阻就做了,低電量的時候系統一直判斷不准確,“低電關機”提示一會有,一會沒有,客戶那個郁悶呀,“到底是有電還是沒電?”。
如下圖,通過兩個分壓電阻,和一個AD腳去偵測VCC(電池)電壓。

一、MAX17040的工作原理
電量計MAX17040,他通過芯片去測量電池電量,芯片本身集成的電路比較復雜,同時可以通過軟件上的一些算法去實現一些處理,是測量出的電量更加准確。還有一個好處,就是他之接輸出數字量,通過IIC直接讀取,我們在電路設計、程序處理上更加的統一化。
如下圖所示,MAX17040和電池盒主控的關系,一個AD腳接到電池VBAT+,檢測到的電量信息,通過IIC傳到主控。

下面是電路圖,電路接口比較簡單,VBAT+,接到max17040的CELL,IIC接到主控的IIC2接口,這個我們在程序中要配置。看這個器件比較簡單吧。

看下max17040的內部結構,其實這也是一個AD轉換的過程,單獨一顆芯片去實現,這樣看起來比較專業些。CELL接口,其實就是一個ADC轉換的引腳,我們可以看到芯片內部有自己的時鍾(time base),IIC控制器之類的,通過CELL采集到的模擬量,轉換成數字量,傳輸給主控。

通過上面的介紹Max17040的硬件、原理我們基本上都了解了,比較簡單,下面我們就重點去分析下驅動程序。
二、MAX17040 總體流程
電量計的工作流程比較簡單,max17040通過CELL ADC轉換引腳,把電池的相關信息,實時讀取,存入max17040相應的寄存器,驅動申請一個定時器,記時結束,通過IIC去讀取電池狀態信息,和老的電池信息對比,如果用變化上報,然后重新計時;這樣循環操作,流程如下所示:

三、MAX17040這個電量計驅動,我們主要用到以下知識點
1、IIC的注冊(這個在TP、CAMERA中都有分析);
2、Linux 中定時器的使用;
3、任務初始化宏;
4、linux定時器調度隊列;
5、max17040測到電量后如何上傳到系統(這個電池系統中有簡要的分析);
6、AC、USB充電狀態的上報,這個和電池電量是一種方法。
7、電池曲線的測量與加入;
1、IIC的注冊
IIC這個總線,在工作中用的比較多,TP、CAMERA、電量計、充電IC、音頻芯片、電源管理芯片、基本所有的傳感器,所以這大家要仔細看下,后面有時間的話單獨列一片介紹下IIC,從單片機時代都用的比較多,看來條總線的生命力很強,像C語言一樣,很難被同類的東西替代到,至少現在應該是這樣的。
看下他結構體的初始化與驅動的申請,這個比較統一,這里就不想想解釋了。
(1)、IIC驅動的注冊:
1 static const struct i2c_device_id max17040_id[] = { 2 { "max17040", 0 }, 3 { } 4 }; 5 MODULE_DEVICE_TABLE(i2c, max17040_id); 6 7 static struct i2c_driver max17040_i2c_driver = { 8 .driver = { 9 .name = "max17040", 10 }, 11 .probe = max17040_probe, 12 .remove = __devexit_p(max17040_remove), 13 .suspend = max17040_suspend, 14 .resume = max17040_resume, 15 .id_table = max17040_id, 16 }; 17 18 static int __init max17040_init(void) 19 { 20 printk("MAX17040 max17040_init !!\n"); 21 wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present"); 22 return i2c_add_driver(&max17040_i2c_driver); 23 } 24 module_init(max17040_init);
(2)在arch/arm/mach-exynos/mach-smdk4x12.c中,IC平台驅動的注冊:
1 static struct i2c_board_info i2c_devs2[] __initdata = { 2 #if defined(CONFIG_BATTERY_MAX17040) 3 { 4 I2C_BOARD_INFO("max17040", 0x36),//IIC地址; 5 .platform_data = &max17040_platform_data, 6 }, 7 #endif 8 …………………… 9 };
下圖就是我們IIC驅動注冊生成的文件;
/sys/bus/i2c/drivers/max17040

2、linux 中定時器的使用
定時器,就是定一個時間, 比如:申請一個10秒定時器,linux系統開始計時,到10秒,請示器清零重新計時並發出信號告知系統計時完成,系統接到這個信號,做相應的處理;

3、任務初始化宏
任務結構體的初始化完成后,接下來要將任務安排進工作隊列。 可采用多種方法來完成這一操作。 首先,利用 queue_work 簡單地將任務安排進工作隊列(這將任務綁定到當前的 CPU)。 或者,可以通過 queue_work_on 來指定處理程序在哪個 CPU 上運行。 兩個附加的函數為延遲任務提供相同的功能(其結構體裝入結構體 work_struct 之中,並有一個 計時器用於任務延遲 )。
4、linux定時器調度隊列
1 INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); 2 schedule_delayed_work(&chip->work, MAX17040_DELAY); 3 通過定時器調度隊列;
5、max17040測到電量后如何上傳到系統(這個電池系統中有簡要的分析);
4中的定時器記時完成,就可以調度隊列,chip->work執行:max17040_work函數,把改讀取的信息上傳,我們看下max17040_work函數的實現:
(1)、保存老的電池信息,如電量、AC、USB是否插入
(2)、讀取電池新的狀態信息
(3)、如果電池信息有變化,就上報系統
power_supply_changed這個函數比較重要, 我們后面分析;
(4)、如果用PM2301充電IC,USB充電功能不用
這個是由於我們的系統耗電比較大,用USB充電時,電流過小,所以出現越充越少的現象,所以這個功能給去掉了。
(5)、如果有DC插入,則跟新充電狀態
6、AC、USB充電狀態怎么更新到應用
如上面所說,通過power_supply_changed上報;
7、電池曲線的測量與加入
電池曲線,就是電池的沖放電信息,就是用專業的設備,對電池連續充放電幾天,測出一個比較平均的值。然后轉換成針對電量IC(如我們用的max17040)的數字量,填入一個數組中,如下圖所示:

下面數據時針對電池曲線的數字量,和相關參數。如上圖所示,為160小時的電池信息,包括:不同顏色分別代表不同的曲線:如temperature ,reference SOC ,fuel gauge SOC,Vcell,Empty Voltage
數據表格如下:
1 Device=MAX17040 2 Title = 1055_2_113012 3 EmptyAdjustment = 0 4 FullAdjustment= 100 5 RCOMP0=161 6 TempCoUp =0 7 TempCoDown = -2 8 OCVTest = 56224 9 SOCCheckA = 113 10 SOCCheckB = 115 11 bits= 18 12 0xC2 0xE8 0x0D 0x37 0x51 0x5B 0x5E 0x62 13 0x6A 0x88 0xA6 0xCB 0xF1 0x3C 0x99 0x1A 14 0x60 0x0D 0x80 0x0D 0xA0 0x01 0xC0 0x0C 15 0xF0 0x0F 0x30 0x0F 0x90 0x06 0x10 0x06 16 17 0xAC 0x20 0xAE 0x80 0xB0 0xD0 0xB3 0x70 18 0xB5 0x10 0xB5 0xB0 0xB5 0xE0 0xB6 0x20 19 0xB6 0xA0 0xB8 0x80 0xBA 0x60 0xBC 0xB0 20 0xBF 0x10 0xC3 0xC0 0xC9 0x90 0xD1 0xA0 21 0x02 0x90 0x0E 0x00 0x0C 0x10 0x0E 0x20 22 0x2C 0x60 0x4C 0xB0 0x39 0x80 0x39 0x80 23 0x0C 0xD0 0x0C 0xD0 0x0A 0x10 0x09 0xC0 24 0x08 0xF0 0x07 0xF0 0x05 0x60 0x05 0x60 25 26 0xC0 0x09 0xE0 0x00 0x00 0x01 0x30 0x02 27 0x52 0x06 0x54 0x0B 0x53 0x080x63 0x08 28 0x29 0xE0 0xC1 0xE2 0xC6 0xCB 0x98 0x98 29 0xCD 0xCD 0xA1 0x9C 0x8F 0x7F 0x56 0x56
加入驅動中的值:
/driver/power/max17040_common.c中
1 unsigned char model_data[65] = { 2 0x40, /* 1st field is start reg address, others are model parameters */ 3 0xAC, 0x20,0xAE, 0x80, 0xB0, 0xD0, 0xB3, 0x70, 4 0xB5, 0x10, 0xB5, 0xB0, 0xB5, 0xE0,0xB6, 0x20, 5 0xB6, 0xA0, 0xB8, 0x80, 0xBA, 0x60, 0xBC, 0xB0, 6 0xBF, 0x10, 0xC3, 0xC0, 0xC9, 0x90, 0xD1, 0xA0, 7 0x02, 0x90, 0x0E, 0x00, 0x0C, 0x10,0x0E, 0x20, 8 0x2C, 0x60,0x4C, 0xB0, 0x39, 0x80, 0x39, 0x80, 9 0x0C, 0xD0,0x0C, 0xD0, 0x0A, 0x10,0x09, 0xC0, 10 0x08, 0xF0, 0x07, 0xF0, 0x05, 0x60, 0x05, 0x60, 11 }; 12 13 unsigned char INI_OCVTest_High_Byte = 0xDB; //56224 14 unsigned char INI_OCVTest_Low_Byte = 0xA0; 15 unsigned char INI_SOCCheckA = 0x71;// 113 16 unsigned char INI_SOCCheckB = 0x73;//115 17 unsigned char INI_RCOMP = 0xa1;//161 18 unsigned char INI_bits = 18; 19 unsigned char original_OCV_1; 20 unsigned char original_OCV_2; 21 #elseunsigned char INI_RCOMP = 0x64; 22 unsigned char INI_bits = 19; 23 unsigned char original_OCV_1; 24 <strong>unsigned char original_OCV_2;</strong>
四、驅動分析
1、Probe函數分析
上面我們簡單了解驅動中用到的主要知識點,后面我們把這些點串起來,驅動還是從probe說起;
1 static int __devinit max17040_probe(struct i2c_client *client, 2 const struct i2c_device_id *id) 3 { 4 struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); 5 struct max17040_chip *chip; 6 int ret; 7 printk("MAX17040 probe !!\n"); 8 9 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) 10 return -EIO; 11 12 chip = kzalloc(sizeof(*chip), GFP_KERNEL); 13 14 if (!chip) 15 return -ENOMEM; 16 17 g_chip = chip; 18 g_i2c_client = client;//(1)、IIC 驅動部分client 申請; 19 20 chip->client = client; 21 chip->pdata = client->dev.platform_data; 22 i2c_set_clientdata(client, chip); 23 chip->battery.name = "battery";//(2)、chip name; 24 chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; 25 chip->battery.get_property = max17040_get_property;//(3)、獲取電池信息; 26 chip->battery.properties = max17040_battery_props;//(4)、電池各種信息; 27 chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props); 28 chip->battery.external_power_changed = NULL; 29 ret = power_supply_register(&client->dev, &chip->battery);//(5)、battery加入power_supply 30 if (ret) 31 goto err_battery_failed; 32 33 34 chip->ac.name = "ac" 35 chip->ac.type = POWER_SUPPLY_TYPE_MAINS; 36 chip->ac.get_property = adapter_get_property; 37 chip->ac.properties = adapter_get_props; 38 chip->ac.num_properties = ARRAY_SIZE(adapter_get_props); 39 chip->ac.external_power_changed = NULL; 40 ret = power_supply_register(&client->dev, &chip->ac);//(6)、和battery相似,把ac加入power_supply 41 if (ret) 42 goto err_ac_failed; 43 44 45 #if !defined(CONFIG_CHARGER_PM2301) 46 chip->usb.name = "usb"; 47 chip->usb.type = POWER_SUPPLY_TYPE_USB; 48 chip->usb.get_property = usb_get_property; 49 chip->usb.properties = usb_get_props; 50 chip->usb.num_properties = ARRAY_SIZE(usb_get_props); 51 chip->usb.external_power_changed = NULL; 52 ret = power_supply_register(&client->dev, &chip->usb);//(7)、和battery相似,把usb加入power_supply 53 if (ret) 54 goto err_usb_failed; 55 56 if (chip->pdata->hw_init && !(chip->pdata->hw_init())) { 57 dev_err(&client->dev, "hardware initial failed.\n"); 58 goto err_hw_init_failed; 59 } 60 #endif 61 62 #ifdef MAX17040_SUPPORT_CURVE 63 g_TimeCount = 0; 64 handle_model(0); 65 #endif 66 max17040_get_version(client); 67 battery_initial = 1; 68 69 INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);//(8)、任務宏初始化,max17040加入chip->work隊列; 70 schedule_delayed_work(&chip->work, MAX17040_DELAY);//(9)、通過定時器調度隊列; 71 72 73 printk("MAX17040 probe success!!\n"); 74 return 0; 75 76 err_hw_init_failed: 77 power_supply_unregister(&chip->usb); 78 err_usb_failed: 79 power_supply_unregister(&chip->ac); 80 err_ac_failed: 81 power_supply_unregister(&chip->battery); 82 err_battery_failed: 83 dev_err(&client->dev, "failed: power supply register\n"); 84 i2c_set_clientdata(client, NULL); 85 kfree(chip); 86 return ret; 87 }
(1)、IIC 驅動部分client 申請;
(2)、chip name;
(3)、獲取電池信息;
通過傳遞下來的參數,來讀取結構體中相應的狀態,這個函數實現比較簡單。
(4)電池各種信息

1 static enum power_supply_property max17040_battery_props[] = { 2 POWER_SUPPLY_PROP_PRESENT, 3 POWER_SUPPLY_PROP_STATUS, 4 /*POWER_SUPPLY_PROP_ONLINE,*/ 5 POWER_SUPPLY_PROP_VOLTAGE_NOW, 6 POWER_SUPPLY_PROP_CAPACITY, 7 POWER_SUPPLY_PROP_TECHNOLOGY, 8 POWER_SUPPLY_PROP_HEALTH, 9 POWER_SUPPLY_PROP_TEMP, 10 };
(5)、battery加入power_supply;

(6)、和battery相似,把ac加入power_supply;
(7)、和battery相似,把usb加入power_supply;
(8)、max17040加入chip->work隊列;
前面已經分析;
(9)、通過定時器調度隊列;
前面已經分析;
2、power_supply_changed簡要分析
如:把電池電量信息上報:我們在max17040_work隊列調度函數中, 如果有電池信息、狀態變化,則上用power_supply_changed上報。
Kernel/drivers/power/power_supply_core.c中:
void power_supply_changed(struct power_supply *psy) { unsigned long flags; dev_dbg(psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags); psy->changed = true; wake_lock(&psy->work_wake_lock); spin_unlock_irqrestore(&psy->changed_lock, flags); schedule_work(&psy->changed_work);//調度psy->changed_work } Psy->changed_work的執行函數: static void power_supply_changed_work(struct work_struct *work) { unsigned long flags; struct power_supply *psy = container_of(work, struct power_supply, changed_work); dev_dbg(psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags); if (psy->changed) { psy->changed = false; spin_unlock_irqrestore(&psy->changed_lock, flags); class_for_each_device(power_supply_class, NULL, psy, __power_supply_changed_work); power_supply_update_leds(psy); kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);//uevent狀態 spin_lock_irqsave(&psy->changed_lock, flags); } if (!psy->changed) wake_unlock(&psy->work_wake_lock); spin_unlock_irqrestore(&psy->changed_lock, flags); }
