This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

BQ27427: Gauge counts backwards, flags backwards too

Part Number: BQ27427
Other Parts Discussed in Thread: BQSTUDIO,

Tool/software:

The gauge is going backwards. I am using the 27427 EVM by TI. Battery hooked up right, charger supply hooked up right.

"backwards": when I discharge (some dummy load), the CHG flag goes high and the current is positive. When I charge, DSG goes high and the current is negative.

The TRM does NOT mention "CC Gain". I read it out anyway. The value is garbage, no matter which byte order I interpret it in. The contents of the CCCAL block are { 0x00, 0x00, 0x0b, 0xd4, 0x7f, 0xb4, 0x65, 0x4e, ... }

What's going on? How do I fix this?

  • Hello, 

    The sign of the CC Gain parameter is probably incorrect, please refer to this E2E thread for how to change the sign of the CC Gain parameter.

    Regards, 

    Robert. 

  • I don't use bqstudio. I work on the basis of the TRM and whatever other docs I manage to find.

    This is an actual problem.

    The chip comes with this bad value from the factory. Clearing the sign to turn it positive is the hotfix. The documentation does not mention this anywhere, does not even document the "CC Gain" register, or its format.

    I found more threads about this in the meantime. I also found more info than you've linked to.

    That's not an IEEE 754 float. It's a "Xemics" float. Whatever that is. Found a 21 year old app note with *Visual Basic* code to encode and decode these things. No actual spec so far.

    TI need to do better.

    On Monday I'll make a post with some illustrations and C code for the poor souls that will run into this issue after me.

  • Alright. I investigated.

    The "CC Gain" parameter is in the CC Cal class (105 decimal, 0x69), at offset 4 (i.e. at 0x40+4), four bytes long. The value's second byte's (0x40+5) high bit is the sign bit. After a reset command (0x0041 / 0x41 0x00), it reads as set, which indicates a negative value. That needs to be positive, so that bit needs clearing.

    Follow the procedure in the Application Examples and change it a little.DataBlockClass (0x3E) needs 0x69 instead of 0x52 (and what they mention elsewhere: you need to sleep for 5 ms after DataBlock() write). Clear the high bit at (0x40+5), recalculate the checksum (or cheat and read it, flip its high bit) and write the checksum.

    Here are the byte values and Python expression for the value I encountered

    CC Gain: Xemics: 7F B4 65 4E -> - 2**( -1-24) * 0xB4654E == -0.3523354

    And this is what I wrote back:

    CC Gain: Xemics: 7F 34 65 4E -> + 2**( -1-24) * 0xB4654E == +0.3523354

    Now the current measurement's sign is correct and the gauge's SOC increases with charge.

    Informational: the floating point value is a "Xemics floating point" format. It consists of four bytes. Interpret as Big Endian when applicable. AFAICS it can't represent inf/NaN and it can't represent the value zero. Some C code:

    uint8_t xemics[4]; // read four bytes into this
    
    int exponent = (int)xemics[0] - 128;
    
    int sign = (xemics[1] & 0x80) ? -1 : +1;
    
    uint32_t mantissa = ((xemics[1] & 0x7F) << 16 | xemics[2] << 8 | xemics[3]) | 0x800000;
    // 23 bits of actual bits, plus the implied 1-bit at position 23, making 24 bits
    // 0x800000 is the implied 1-bit of the mantissa, so it looks like 1bbb...bbb
    
    float value = sign * ldexp(mantissa, exponent - 24);
    // -24 so the mantissa is valued like 0.1bbb... (0.5 <= value < 1.0)
    // and then the exponent is applied

  • And since I saw some conversion code in Python posted in this forum, here's my own interpretation, which works exactly, and checks for incompatible values.

    import math
    import struct
    import numpy as np
    
    def build_xemics(sign, exponent, mantissa):
        assert sign in (0, 1), sign
        assert -128 <= exponent < 128, exponent
        assert mantissa & 0x800000, "where is the leading one?"
        mantissa &= ~0x800000 # clear the leading 1 (implicit)
        assert 0 <= mantissa, "mantissa is negative"
        assert mantissa < 2**23, "mantissa has some bits on integer side"
    
        return bytes([
            exponent + 128,
            ((mantissa >> 16) & 0x7F) | (sign << 7),
            ((mantissa >>  8) & 0xFF),
            ((mantissa >>  0) & 0xFF),
        ])
    
    def xemics_from_fp32(value):
        if np.isinf(value):
            if value > 0:
                return build_xemics(0, 127, 0xFFFFFF)
            else:
                return build_xemics(1, 127, 0xFFFFFF)
        elif np.isnan(value):
            raise ValueError("NaN not supported")
    
        sign = np.signbit(value) # (value < 0) except 0 can have a sign, so this also copies that
        if sign: value = -value
        (mantissa, exponent) = np.frexp(value)
    
        mantissa = np.ldexp(mantissa, 24)
        mantissa = round(mantissa)
    
        if value == 0: # xemics can't express 0.
            exponent, mantissa = -128, 0x800000 # that's closest to 0
    
        # IEEE figures 2^exp * 1.mmm, exp being -126 to +127 (bias 127, special values for -127 and 128)
        # Xemics figures 2^exp * 0.1mmm, exp being -128 to +127 (bias 128, no specials)
        # so IEEE floats can express larger numbers than xemics
        # (2^127 * 1.mmm = 2^128 * 0.1mmm)
        # in that case we can only clip
        if exponent >= 128:
            exponent, mantissa = 127, 0xFFFFFF
    
        assert mantissa & 0x800000 != 0, "by construction, there should have been a leading 1"
        assert mantissa <= 0xFFFFFF, "rounding up was a mistake?"
    
        return build_xemics(sign, exponent, mantissa)
    
    def fp32_from_xemics(value_xemics):
        assert isinstance(value_xemics, (bytes, bytearray))
        assert len(value_xemics) == 4
        exponent = value_xemics[0]
        mantissa = (value_xemics[1] << 16) | (value_xemics[2] << 8) | (value_xemics[3] << 0)
        sign = bool(mantissa & 0x800000) # 0 = positive, 1 = negative
        mantissa |= 0x800000 # implied 1.xxx
        result = np.ldexp(mantissa, exponent - 128 - 24)
        if sign: result = -result
        # result = result.astype(np.float32)
        return result
        
    if __name__ == '__main__':
        value_float = np.float32(np.pi) # any reasonable value
        assert fp32_from_xemics(xemics_from_fp32(value_float)) == value_float
    
        value_xemics = bytes.fromhex("82 49 0f db") # that happens to be pi
        assert xemics_from_fp32(fp32_from_xemics(value_xemics)) == value_xemics

  • Another piece of the puzzle that is BQ27427: if you want to write stuff like the Chemistry ID, design capacity, ... don't read back right after writing. Soft-reset first, which you need to anyway. Only after the soft-reset will you read back what you just wrote. If you read back before the soft-reset, it'll be the old value. It's like reading the effective value.