Mer/Documentation/BME Protocol

This documentation is entirely for the purpose of implementing software gathering battery information on Nokia Internet Tablets, not alteration of battery management state.

Existing code:

General protocol:

  • UNIX socket connection to /tmp/.bmesrv
  • Client: send('BMentity')
  • Server: send('\n')

After handshake:

typedef struct {
    uint16      type, subtype;
} BMEHeader;

types so far (NAME, (type, subtype)):


EM_BATTERY_INFO_REQ (0x06, 0x00)

typedef struct {
    BMEHeader header;
    u_int32_t flags;   // Set to 0xFFFFFFFF to get all data

struct emsg_battery_info_reply {
    uint32      a;
    uint32      flags;
    uint16      c;
    uint16      d;
    uint16      temp;   // Battery temperature measured in Kelvin 
    uint16      f;
    uint16      g;
    uint16      h;
    uint16      i;
    uint16      j;
    uint16      k;
    uint16      l;

Some BULK0 message (0x42, 0x00)

typedef struct {
    BMEHeader header;
    u_int32_t flags;   // Set to 0xFFFFFFFF to get all data

struct emsg_bme_bulk_reply {
   uint32     unknown1;  // (Apparently always 0.)
   uint32     unknown2;  // (Apparently always 0.)
   uint32     unknown3;  // (Apparently always 0.)
   uint16     sw_status; // Battery monitor SW status.  (Values 0..3 encountered.)
   uint16     instaneous_battery_voltage; // Instantaneous battery voltage (mV)
   uint16      // Remaining standby time to battery low (mins)
   uint16     unknown4;  // (Apparently always 0.)
   uint16     unknown5;  // (Values 0..24 encountered, with 0 being by far most common.)
   uint16     unknown6;
   uint16     // Battery monitor check voltage (mV)
   uint16     // Battery low warning interval counter.  (Values 0..4 encountered, with 0 being by far most common.)
   uint16     // Double median filtered battery voltage
   uint16     // Initial battery monitor voltage (mV)
   uint16     // Time per battery bar (mins).  Apparently always 2520.
   uint16     // DMF voltage sampled at first battery low (mV)
   uint32     // Average phone current (uA)
   uint16     // Most recent battery charge condition (mAh)
   uint16     // Lowest TX-Off voltage (mV)
   uint16     // Lowest TX-On voltage (mV)
   uint16     // Largest TX-Off/On voltage difference (mV)
   uint8      // Battery bar level log mask
   uint8      // Previous battery bar level.  (Values 0..4 encountered.)
   uint8      // Battery low reason.  (Values 0,1 encountered.)
   uint8      // CS state information.  (Apparently always 1.)
   uint16     // Number of battery bars.  (Values 1..4.)
   uint16     // Battery type.  (Value 4 for me, with standard Nokia battery.)
   uint16     // Temperature, in kelvin
   uint16     // Battery capacity 
   uint16     // Battery impedance (mOhm).  (Apparently always 160.)
   uint16     // Present value of v_bat_full_level.  (Apparently always 3855.)
   uint16     // Present value of v_bat_low_ths_mv.  (Apparently always 3650.)
   uint16     unknown7;  (Values 0..21 encountered.)
   uint16     unknown8;  (Apparently always 0.)
   uint16     unknown9;  (Apparently always 0.)
   uint16     // Load current estimated by Batmon4 (uA).  (Various values in 0..125 encountered.)
   uint16     unknown10;  (Apparently always 0.)

The “Instantaneous battery voltage (mV)” field’s observed values have a certain regular spacing indicating a linear function of some integer value with a smaller range, presumably retu adc register #8. If it is indeed from that register, then note there is a non-zero offset involved: one function that gives values consistent with the set of values observed and that somewhat matches one set of observations of both fields (which unfortunately had a non-negligible delay between sampling the two) is mV = 2252.362 + 2.81361 * adc#8 (where the multiplier is fairly accurate, while the constant is accurate to within 2 units in the last place; the choice of 2252.362 rather than some integer multiple of 2.81361 away from there is based on how well ioctl values match with values read from bme at about the same time, though there was still quite a bit of noise, and the methodology could be improved).

One might wonder how bme authors came up with numbers like 2252.362 and 2.81361. One hypothesis would be that someone measured a voltage for two adc8 values (perhaps one near the top of the range and one near the bottom), and used a straight-line fit for the rest. If the mV field is calculated by rounding to nearest int rather than flooring, and the two voltage measurements were both an integer number of mV, then there is exactly one pair of integer points that matches the data: adc8 of 238 was 3222mV, while 576 was 4173mV. Thus, without yet having looked for other simple models, it seems most likely that this mV field is calculated by some expression equivalent to mV = 3222 + round((adc8 - 238) * ((4173 - 3222) / (576 - 238.))), i.e. mV = 3222 + round((adc8 - 238) * (951 / 338.)); where the rounding function can either be C99 round or floor(x+.5), or rint with some rounding modes, so long as 3697.5 rounds to 3698.

Accordingly, some uses of this “instantaneous voltage” field would do well to either convert to an integer adc8 value (or convert to an integer adc8 value then convert back to a non-integer mV value), in order to avoid uneven steps between values (sometimes 2mV between neighbouring values and sometimes 3mV). This can be done as adc8 = 238 + round((claimed_mV - 3222) * (338 / 951.)).

The “Battery monitor check voltage (mV)” field has similar values to the “Instantaneous battery voltage (mV)” field, but without obvious regular spacing. I (pjrm) don't know how it's calculated.

Voltage levels depend not just on amount of charge remaining in the battery, but also recent electrical load: the voltage level goes down markedly while the CPU is being heavily used, for example.

There is some weak evidence that the voltage level goes down by about 1mV for each 3 units of backlight level (so 40–50mV for full brightness compared to screen off). The main weakness of this evidence is that there's no serious correction for the effect of CPU usage on voltage, and one would assume CPU usage to correlate with backlight.

“Time per battery bar” seems to be a constant 2520; edit this page if you've seen a different value.

The “Average phone current” field has some strangeness in that if there is a long period of high CPU activity then it takes 3-7 seconds before this field's value increases (even though the voltage field drops straight away, suggesting that the true current does drop straight away); but the field is much quicker to go down once the CPU activity stops, so it's not just a matter of always lagging by a few seconds (such as because of using a simple averaging/smoothing method). One might suggest that this could be because some thread that updates the average is at low priority and doesn't get scheduled for a while, but the "CPU activity" in the experiment consisted of alternating ~100ms of full CPU followed by a usleep of 100ms, so this effect presumably isn't just because of standard kernel scheduling decisions and niceness.

“Number of battery bars” is something like min(4, ceil(standby-time-remaining / time-per-bar)), but with a bit of lag: sometimes time-per-bar * n-bars can exceed standby-time-remaining by 2522 or so. (Where of course standby-time-remaining means the value of the “Remaining standby time to battery low (mins)” field, time-per-bar means the “Time per battery bar” field, and n-bars means the “Number of battery bars” field.) Note the ‘min(4,’ part: standby-time-remaining can be over 4000mins more than the product of those two fields would suggest.

Some BULK1 message (0x43, 0x00)

typedef struct {
    BMEHeader header;
    u_int32_t flags;   // Set to 0xFFFFFFFF to get all data

struct emsg_bme_bulk1_reply {
   uint32     unknown1;
   uint32     unknown2;
   uint32     unknown3;
   uint16     // Elapsed model time (min)
   uint16     // Tx-Off battery voltage (mV)
   uint16     // Tx-On battery voltage (mV)
   uint8      // Battery power state
   uint8      //  Batmon4 internal flags2
   uint8      // Batmon4 internal flags3
   uint8      // Charging method
   uint16     // Present Phi value (mV)
   uint16     // Present Delta Phi value (mV)
   uint8      // Charging mode 
   uint8      // Previous charging mode 
   uint8      // Charger type
   uint8      // Previous charger type
   uint16     // Instantaneous battery voltage (mV)
   uint8      // Number of charger checks (0-9) ?
   uint8      // Charger recognition state
   uint16     unknown4;
   uint16     // Instantaneous charger current (mA)
   uint16     unknown5;
   uint16     // Charging time (min)
   uint16     // Average Vchar (mV)
   uint16     // Equivalent DC charger current (mA) 
   uint8      // Battery full flag (0 or 1)
   uint8      // HW Cha PWM value L ??
   uint8      // Cha PWM value L ??
   uint8      unknown6;
   uint16     // Open switch battery voltage (mV)
   uint16     // Closed switch battery voltage (mV)
   uint16     unknown7;

Some BULK2 message (0x44, 0x00)

typedef struct {
    BMEHeader header;
    u_int32_t flags;   // Set to 0xFFFFFFFF to get all data

struct emsg_bme_bulk2_reply {
   uint32     unknown1;
   uint32     unknown2;
   uint32     unknown3;
   uint16     // Conf. battery footprint
   uint16     // Conf. minimum standby current (mA)
   uint16     // Conf. Batmon battery low voltage (Safety level) 
   uint16     // Conf. Batmon battery low voltage (Empty)
   uint16     // Configured number of battery bars
   uint16     unknown4;

Other information about battery life

Some battery-related information is also available from retu adc registers (which in turn are available via ioctl's on /dev/rutu: see retu-adc source).

It's said that register 3 shows whether charging is occurring: with value 0 meaning "not being charged", while "values 0xff and above" are an indication of the voltage being applied for charging. Matan adds ( that values around 0x100 indicate charging, while values around 0x170 indicate that the power source is connected but we aren't charging.

Register 8 apparently indicates battery voltage. See the note above about “instantaneous battery voltage (mV)” for how it might correspond to one measure of voltage.

Note that this value can fluctuate a lot when the device is in use, but when idle it gets a good fit to the curve adc#8 = 438.3 + exp(p0 + p1*t) - exp(n0 + n1*t) where t is time (or more generally charge used or remaining), and p0,p1,n0,n1 are constants. (I forget the values I found for them, but in any case it would be good to fit for them again once more data is on hand.)

To invert this function to find idle time remaining (on assumption that the device was idle when the register was read), first see whether the adc#8 value is above or below 438.3, which tells you which exponential is dominant, and approximate the other exponential with a constant 3 (or a straight line if you like); can iterate and update this approximation for the neighbourhood of the solution found by the previous iteration. -- pjrm