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;
   uint32     unknown2;
   uint32     unknown3;
   uint16     sw_status; // Battery monitor SW status
   uint16     instaneous_battery_voltage; // Instantaneous battery voltage (mV)
   uint16      // Remaining standby time to battery low (mins)
   uint16     unknown4;
   uint16     unknown5;
   uint16     unknown6;
   uint16     // Battery monitor check voltage (mV)
   uint16     // Battery low warning interval counter
   uint16     // Double median filtered battery voltage
   uint16     // Initial battery monitor voltage (mV)
   uint16     // Time per battery bar (mins)
   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
   uint8      // Battery low reason
   uint8      // CS state information 
   uint16     // Number of battery bars
   uint16     // Battery type
   uint16     // Temperature, in kelvin
   uint16     // Battery capacity 
   uint16     // Battery impedance (mOhm)
   uint16     // Present value of v_bat_full_level
   uint16     // Present value of v_bat_low_ths_mv
   uint16     unknown7;
   uint16     unknown8;
   uint16     unknown9;
   uint16     // Load current estimated by Batmon4 (uA)
   uint16     unknown10;

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, but the constant could be out by some multiple of 2.81361). -- pjrm.

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

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.

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.

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