I recently bought a replacement battery for my Acer (Aspire 3820TG-5454G50n to be exact) laptop from TradeShop in EBay.
After receiving it and inserting it into the laptop nothing happens, the charging led starts immediately flashing like there were a problem. The problem was that BIOS did not recognize the battery, nothing was visible in BIOS nor in Linux. It was like there were no battery attached at all. Even though the laptop was able run on battery it did not receive any charge.
Searching for the net and various forums for a solution, one suggestion repeating itself was to update to the latest BIOS from Acer. Downloaded BIOS version 1.19 from Acer, created DOS rescue USB stick and reflashed the BIOS… Update was successful but no luck with the battery, still not recognized nor charging.
Browsing the forums led to the schematics of the laptop [1] which showed that the battery is handled by a separate microcontroller called KBC and it talks to battery over i2c bus.
In the BIOS update package there were a sub-directory KBC with an executable and a bin-file. Looking at the bin file, named W07AC115.bin
, it looked very much like it was the firmware image for KBC. It was not encrypted so it was easy to see what is was made of. It contained something very much like battery names:
% strings W07AC115.bin | grep AS10 !AS10B3E AS10B5E !AS10B6E !AS10B7E !AS10E7E !AS10E76 !AS10E36
The original battery type is AS10E7E and the replacement is AS10E76 and both names seem to be included in the firmware image. So things should work just fine.
Obviously the BIOS cannot read the sticker on the battery but gets the battery name, type and other information from KBC which in turn talks to the battery over i2c bus using SBS (Smart Battery Specification) [2] protocol. So maybe the battery identifies itself with some other, non-compatible, name? Or the replacement battery is just broken and should be replaced.
Arduino UNO i2c battery reader
The SBS protocol is very simple, writing command bytes and reading reply back. The battery connector pin order was in the schematics:
1 2 3 4 5 6 7 8 ++ X X X X X X X X -- pin 1 2 +12.6V / 1.5 - 2.0A 5 SCL 6 SDA 7 8 GND
All that needs to be done is wire i2c and GND (battery has its own power) from Arduino to the battery connector. The interface board consists just two 1kOhm pull-up resistors to 3.3V from Arduino I/O pins to battery connector pins.
Arduino Battery 3v3 -----+--+ | | R R 1kOhm | | SCL -----|--+------- SCL (5) SDA -----+---------- SDA (6) GND ---------------- GND (8)
Using Arduino software based i2c library SoftI2CMaster [3] gives freedom to use any available I/O pin for i2c instead of dedicated but fixed i2c pins.
Requesting and reading strings and integers from the battery is done by using the commands from SBS. Battery is the device 11 on the i2c bus, all other addresses return nothing.
#define SDA_PIN 3 #define SDA_PORT PORTD #define SCL_PIN 2 #define SCL_PORT PORTD #define I2C_VERYSLOWMODE 1 #define I2C_TIMEOUT 2000 #define I2C_NOINTERRUPT 1 #include "SoftI2CMaster/SoftI2CMaster.h" #define VOLTAGE 0x09 #define TEMPERATURE 0x08 #define CURRENT 0x0a #define CAPACITY 0x10 #define TIME_TO_FULL 0x13 #define CHARGE 0x0d #define STATUS 0x16 #define CYCLE_COUNT 0x17 #define MANUFACTURE_DATE 0x1b #define MANUFACTURER_NAME 0x20 #define DEVICE_NAME 0x21 #define DEVICE_CHEMISTRY 0x22 static byte deviceAddress = 11; static byte buf[64]; static int fetchString(byte func) { i2c_start(deviceAddress<<1 | I2C_WRITE); i2c_write(func); i2c_rep_start(deviceAddress<<1 | I2C_READ); byte i; byte size; size = i2c_read(false); if (size > sizeof(buf)-1) size = sizeof(buf)-1; for (i = 0; i < size-1; i++) { byte b1 = i2c_read(false); buf[i] = b1; } byte b1 = i2c_read(true); buf[i] = b1; buf[i+1] = 0; i2c_stop(); return size; } static int fetchWord(byte func) { i2c_start(deviceAddress<<1 | I2C_WRITE); i2c_write(func); i2c_rep_start(deviceAddress<<1 | I2C_READ); byte b1 = i2c_read(false); byte b2 = i2c_read(true); i2c_stop(); return (int)b1|((( int)b2)<<8); }
Then it is just a matter of asking and printing replies back, like:
printString("Device name: "); fetchString(DEVICE_NAME); printString((const char *)buf); printNewline();
Source code for Arduino Uno is available in Github [4]. SoftI2C library had to be set running with slowest bitrate and even then there were occasional data transmission errors. A simple patch for enabling even slower speed resolved the issue:
diff --git a/SoftI2CMaster.h b/SoftI2CMaster.h index 7393a8d..af70577 100644 --- a/SoftI2CMaster.h +++ b/SoftI2CMaster.h @@ -159,9 +159,13 @@ uint8_t __attribute__ ((noinline)) i2c_read(bool last); #if I2C_SLOWMODE #define I2C_DELAY_COUNTER (((I2C_CPUFREQ/25000L)/2-19)/3) #else +#if I2C_VERYSLOWMODE +#define I2C_DELAY_COUNTER (((I2C_CPUFREQ/12000L)/2-19)/3) +#else #define I2C_DELAY_COUNTER (((I2C_CPUFREQ/100000L)/2-19)/3) #endif #endif +#endif // Table of I2C bus speed in kbit/sec: // CPU clock: 1MHz 2MHz 4MHz 8MHz 16MHz 20MHz
Using the above quickly whipped up rig revealed that the battery identifies itself as AS10B41, which is not in the above list of battery names supported by the KBC firmware.
Patching the KBC
Changing one of the battery names in the KDC firmware image file would be an easy solution, unless the firmware is protected by a checksum or something. As the KBC controller is responsible of the keyboard, mousepad, fans, and about everything messing with it just might brick the whole laptop.
In some of the discussion forum thread had quite definite consensus that there is no checksum at all. So I changed one battery name (using emacs hexl-mode) to AS10B41 and copied it over to the DOS USB stick in KBC sub-directory as W07AC115.bin
(first making backup of the original firmware file). After booting from the USB stick to DOS mode, reflashing only the KBC by running the KBC.BAT.
Instantly after hitting return, it was very obvious that things were not good… fans went to full blow, screen flickered, mousepad not working. Luckily keyboard was sort of still working and I had the original image in the USB stick. Renamed it back to W07AC115.bin
and ran KBC.BAT again. Instantly things were back to normal. Maybe there is a checksum after all…
Going through the forums more another checksum candidate was coming up, a simple byte-wise modulo 256 sum of all bytes from range between 0x8000 – 0x20000 should add up to 92 (dec). At least for the original file that was correct:
% ruby -e 'puts IO.binread("W07AC115.bin", 0x20000-0x8000, 0x8000).each_byte.reduce(0, &:+) & 0xff' 92
So a change from AS10E76 to AS10B41 introduces a difference of 11 in the checksum and which must be compensated.
% echo -n E76 B41 | hexdump -C 00000000 45 37 36 20 42 34 31 |E76 B41| % echo $(((0x45 + 0x37 + 0x36) - (0x42 + 0x34 + 0x31))) 11
In the same forum thread there were speculation of a certain address which contains the checksum byte, but instead of changing a random byte at random address, why not sacrifice one more battery name by having one of its letters to be incremented by 11. Changing e.g. AS10E36 to LS10E36 would do, as the difference between ‘A’ and ‘L’ is 11. Rechecking the checksum of the changed firmware binary using above Ruby script showed it was correct.
Then reflashing the modified KBC firmware again as above, and… battery was recognized and everything was ok with the laptop! And the battery gets charged! And Linux also knows about it:
% grep . /sys/class/power_supply/BAT0/{model*,manu*,charge*,tech*} /sys/class/power_supply/BAT0/model_name:AS10B41 /sys/class/power_supply/BAT0/manufacturer:SONYCorp /sys/class/power_supply/BAT0/charge_full:6394000 /sys/class/power_supply/BAT0/charge_full_design:6600000 /sys/class/power_supply/BAT0/charge_now:6161000 /sys/class/power_supply/BAT0/technology:Li-ion
References
If the links get broken try to google the filename.
[1] acer_aspire_3820_3820g_3820t_3820tg_wistron_jm31-cp_rev_-1_sch.pdf
[2] sbdat110.pdf
[3] SoftI2CMaster library
[4] Arduino code in Github