element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Embedded and Microcontrollers
  • Technologies
  • More
Embedded and Microcontrollers
Blog Repairing a vacuum cleaner in the most complicated way (plus BQ25798 mini-review)
  • Blog
  • Forum
  • Documents
  • Quiz
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Embedded and Microcontrollers to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: vmate
  • Date Created: 22 Aug 2024 8:02 PM Date Created
  • Views 2081 views
  • Likes 13 likes
  • Comments 7 comments
  • charging
  • repair
  • embedded
  • battery
  • rp2040
  • microcontroller
Related
Recommended

Repairing a vacuum cleaner in the most complicated way (plus BQ25798 mini-review)

vmate
vmate
22 Aug 2024
Repairing a vacuum cleaner in the most complicated way (plus BQ25798 mini-review)

I have this battery powered vacuum cleaner that I quite like, but it decided to stop working.

It uses 3x 18650 cells for power, and has two brushed motors, one for sucking air, the second for a rotating brush in the bottom.

Vacuum cleaner in its "full" configuration

It can also be converted to a handheld vacuum:

Vacuum cleaner converted to handheld mode

The thing is completely dead, it doesn't turn on, doesn't light up, doesn't charge, nothing.
Took it apart, this is the PCB inside:


Original PCB

After some probing, it seems like the microcontroller is dead, so this entire board is trash.
I couldn't find a replacement one, so time to make something.

Custom PCB attempt 1

The functionality seems relatively simple:
- Charge/manage a 3S Li-Ion battery pack
- Turn the motors on and off

The sane way of doing this would probably be an off the shelf BMS with a charger module, and a relay + flip-flop to turn the motors on and off. That seems way too easy though, so doing it the hard way it is.


Here's the initial idea:

- BQ7791501 for battery balancing and protection. It's a neat little IC, supports 3-5S batteries, but multiple can be chained for larger packs. Does balancing and protection automagically without the need for a host microcontroller.

- BQ25798 for charging. I've had my eyes on this IC for a different project, so decided to test it out with this board. It takes 5-24V input, charges a 1-4S battery at 5A max, supports MPPT, has an ADC and can measure pretty much everything, all charging parameters and stages are configurable over I2C.  Saying that it's overkill for this application is an understatement, but maybe I'll really need MPPT solar charging for my vacuum cleaner one day.

- RP2040 to control everything. Had a bunch of them, so why not. Pretty much any MCU would be sufficient for this project.

- Two MOSFETs to control the motors.


Some additional fancy features:

- WS2812B RGB LED instead of the single color one on the original board. There was no way to tell how charged up the battery was, which was quite annoying, so I wanted to use the RGB LED to indicate state-of-charge with colors.

- I didn't want to suffer trying to figure out battery state-of-charge manually, so I decided to use a BQ34Z100.

There are 4 contacts on the back of the unit:
Contacts on the base

There's a common ground, charger input voltage, positive terminal of the brush motor in the bottom, and a pin coming from the two buttons at the top. Pressing the top button connects a 4.7k resistor between ground and this pin, while the bottom button uses 1k.

This is quite simple to use, I'll just need to make a voltage divider where one half is this 4.7k/1k resistor, and read the output voltage with the RP2040's ADC.

I also needed a 3.3V regulator to power things.

Here's the new PCB, next to the original one:


New and original PCBs

I somehow messed up the 3.3V regulator part, so bodged on a linear regulator instead.

New PCB annotated with major functional blocks

Fuel gauge issues

Turns out, the BQ34Z100 was a horrible idea. It looked like exactly what I need, but after writing a bunch of code for it, I realized there were no registers for configuring cell parameters. The only way to do that, is to buy TI's very expensive proprietary programming dongle thing and use their software.

Since I messed up the 3.3V regulator, and the motor driver part was kinda horrible and underpowered as well, I decided to rework the board a bit, and order a new version.

Custom PCB attempt 2

- I switched out the regulator for an IC I've used before, a TPS566231.
- Found a promising fuel gauge, the MAX17261
- Also added an INA226 to monitor pack voltage and current, in case the new fuel gauge doesn't work out either, so I can try to calculate battery SoC in software on the RP2040 as a makeshift fuel gauge.
- Made a proper motor driver section. Instead of driving the FET gates at 3.3V, I added simple gate drivers with an N-channel/P-channel MOSFET pair, to be able to use the full battery voltage to drive the main FET gate. This also let me use way smaller and cheaper FETs for actually driving the motors.

This version finally seemed to work, other than a very slight, major issue with the fuel gauge/INA226. I wanted to share one shunt resistor for the BMS, fuel gauge, and INA226, which turned out to be a bad idea.

Schematic of BMS


The BMS uses the battery pack's negative terminal as its ground, and the rest of the board is connected to pack ground through back-to-back N-MOSFETs, which the BMS controls.

Normally, this isn't a problem. The MOSFETs have very low on resistance, so pack ground and board ground is essentially the same. The issue is when the motors start running, and 15A goes through the two MOSFETs.

Each FET has an RDSon of 18mOhm, which means there will be a 0.54V drop through them when drawing 15A. Since the shunt resistor is before the FETs, it means there will be a common mode voltage of -0.54V at the two shunt input pins of the fuel gauge. The MAX17261 datasheet specifies an absolute maximum rating of -0.3V to Vbatt+0.3V.

The solution is to add a second shunt resistor AFTER the N-FETs. Fortunately, I was able to bodge in a new shunt and rewire the fuel gauge and INA226.

image

I completely forgot about the two buttons, but I did add pads for some unused GPIOs on the RP2040, and some of them happened to be ADC pins, so I only had to bodge a resistor on.

Here are the schematics for various parts of the PCB (I did not correct the shunt resistor issue here, so if you copy this, make sure to fix it as described above):

Motor driver schematic

Schematic of RP2040 microcontroller and various peripherals/misc devices

3.3V buck converter schematic

Schematic of fuel gauge and INA226

Charging circuit schematic

Software

I ended up writing a relatively feature-complete library for the BQ25798, I'll try and clean it up some time later and release it publicly for others to use.
The MAX17261 ended up working perfectly, although the datasheets were a bit disorganized.

The code isn't too complicated, all it has to do is read battery SoC from the MAX17261 and set the RGB LED to some color based on the value when the vacuum cleaner is on or charging. The power buttons simply toggle the two FETs on or off, the second button on the "base" switches to a "low power" mode by using 50% duty cycle PWM to drive the FETs instead of always on. Some parts of the code ended up a bit janky, so I'm not posting it (yet), if I have time to clean that up as well, I'll edit this blog to include it.

Conclusion

Overall, I'm very happy with how everything turned out, even though it would've probably been cheaper to buy another used vacuum cleaner. At least if anything fails now, I can fix it easier.

The MAX17261 "just works", doesn't have any over-complicated features, just set a few basic parameters and it gives you SoC.

The BQ25798 is amazing as well, the datasheet is great, every detail is explained superbly, all the registers are documented with examples, the IC is very configurable and easy to use, and does its job without any problems. All the charging states are configurable and can even be disabled completely, thanks to this I was able to reconfigure one of them to charge lead-acid batteries as well(this is officially not supported, only Li/NiMH). The built-in ADC can monitor input voltage/current, battery voltage, charging current, IC temperature, battery thermistor over I2C.

The only complaints I have about it are the package/footprint, and their MPPT implementation. There is no thermal pad at the bottom, so getting heat away from the IC is very difficult. It uses a weird TI package I've never seen before:
image
The lack of surface area for conducting heat away means it's basically impossible to cool the chip when it's doing over 3.5A output current. In a different project I'm working on, I tried to optimize the PCB layout for cooling, added a heatsink on top of the IC and on the underside of the PCB as well, with active cooling from both sides too, and the maximum I was able to achieve is slightly under 4A continuous. The IC claims to be able to do 5A max, so this is a bit disappointing.

Another slight annoyance is the MPPT implementation, instead of doing "true" MPPT, it measures open-circuit voltage of the solar panel every once in a while, and uses a percentage of that as the target voltage(this percentage, and the measurement interval are configurable over I2C). This works okay-ish, it's definitely way better than no MPPT at all, but "true" MPPT would be so much nicer(like the BQ25756 has).

  • Sign in to reply

Top Comments

  • michaelkellett
    michaelkellett 10 months ago +1
    Thanks for sharing this. It's nice to see a real project, warts and all. I always tell customers to plan for at least one pcb iteration, it doesn't always happen but it often does. I'm puzzled - your…
  • scottiebabe
    scottiebabe 10 months ago +1
    Wonderful project & write up. There is a good chance the battery charger is a flipchip QFN (FC-QFN or Hotrod QFN) https://www.ti.com/lit/an/slvaee1/slvaee1.pdf?ts=1724346665062 A low impedance thermal…
  • DAB
    DAB 10 months ago +1
    Ah, but the adventure was priceless. Nice fix.
  • vmate
    vmate 2 months ago in reply to PhilippGrab

    Thanks!

    I didn't do much with it since, but it's relatively functional as-is. You'll probably have to change a few lines related to the TwoWire class(the I2C class used in the arduino-pico framework, which I used for this project), but it should be quite simple.

    #pragma once
    #include <Wire.h>
    
    enum BQ25798Watchdog
    {
        WD_DISABLED = 0b000,
        WD_500MS = 0b001,
        WD_1S = 0b010,
        WD_2S = 0b011,
        WD_20S = 0b100,
        WD_40S = 0b101,
        WD_80S = 0b110,
        WD_160S = 0b111
    };
    
    enum BQ25798ADC
    {
        ADC_15BIT = 0b00,
        ADC_14BIT = 0b01,
        ADC_13BIT = 0b10,
        ADC_12BIT = 0b11,
        ADC_DISABLED = 0b100,
    };
    
    
    enum BQ25798MPPTVOCRatio
    {
        VOCPERCENT_0_5625 = 0,
        VOCPERCENT_0_625 = 1,
        VOCPERCENT_0_6875 = 2,
        VOCPERCENT_0_75 = 3,
        VOCPERCENT_0_8125 = 4,
        VOCPERCENT_0_875 = 5,
        VOCPERCENT_0_9375 = 6,
        VOCPERCENT_1_0 = 7,
    };
    
    enum BQ25798MPPTVOCDelay
    {
        VOCDELAY_50MS = 0,
        VOCDELAY_300MS = 1,
        VOCDELAY_2S = 2,
        VOCDELAY_5S = 3
    };
    
    enum BQ25798MPPTVOCInterval
    {
        VOCINTERVAL_30S = 0,
        VOCINTERVAL_2M = 1,
        VOCINTERVAL_10M = 2,
        VOCINTERVAL_30M = 3
    };
    
    enum BQ25798ThermalRegulation
    {
        TREG_60C = 0,
        TREG_80C = 1,
        TREG_100C = 2,
        TREG_120C = 3
    };
    
    enum BQ25798ThermalShutdown
    {
        TSHUT_150C = 0,
        TSHUT_130C = 1,
        TSHUT_120C = 2,
        TSHUT_85C = 3
    };
    
    union BQ25798Status0 {
        struct
        {
            bool VBUS_PRESENT_STAT : 1;
            bool AC1_PRESENT_STAT : 1;
            bool AC2_PRESENT_STAT : 1;
            bool PG_STAT : 1;
            bool __rsvd0 : 1;
            bool WD_STAT : 1;
            bool VINDPM_STAT : 1;
            bool IINDPM_STAT : 1;
        };
        uint8_t raw;
    };
    
    union BQ25798Status1 {
        struct
        {
            bool BC1_2_DONE_STAT : 1;
            uint8_t VBUS_STAT : 4;
            uint8_t CHG_STAT: 3;
        };
        uint8_t raw;
    };
    
    union BQ25798Status2 {
        struct
        {
            bool VBAT_PRESENT_STAT : 1;
            bool DPDM_STAT : 1;
            bool TREG_STAT : 1;
            uint8_t __rsvd0: 3;
            uint8_t ICO_STAT: 2;
        };
        uint8_t raw;
    };
    
    union BQ25798Status3 {
        struct
        {
            bool __rsvd0 : 1;
            bool PRECHG_TMR_STAT : 1;
            bool TRICHG_TMR_STAT : 1;
            bool CHG_TMR_STAT : 1;
            bool VSYS_STAT : 1;
            bool ADC_DONE_STAT : 1;
            bool ACRB1_STAT : 1;
            bool ACRB2_STAT : 1;
        };
        uint8_t raw;
    };
    
    union BQ25798Status4 {
        struct
        {
            bool TS_HOT_STAT : 1;
            bool TS_WARM_STAT : 1;
            bool TS_COOL_STAT : 1;
            bool TS_COLD_STAT : 1;
            bool VBATOTG_LOW_STAT : 1;
            bool __rsvd0 : 3;
        };
        uint8_t raw;
    };
    
    union BQ25798Fault0 {
        struct
        {
            bool VAC1_OVP_STAT : 1;
            bool VAC2_OVP_STAT : 1;
            bool CONV_OCP_STAT : 1;
            bool IBAT_OCP_STAT : 1;
            bool IBUS_OCP_STAT : 1;
            bool VBAT_OVP_STAT : 1;
            bool VBUS_OVP_STAT : 1;
            bool IBAT_REG_STAT : 1;
        };
        uint8_t raw;
    };
    
    union BQ25798Fault1 {
        struct
        {
            uint8_t __rsvd0 : 2;
            bool TSHUT_STAT : 1;
            bool __rsvd1 : 1;
            bool OTG_UVP_STAT : 1;
            bool OTG_OVP_STAT : 1;
            bool VSYS_OVP_STAT : 1;
            bool VSYS_SHORT_STAT : 1;
        };
        uint8_t raw;
    };
    
    class BQ25798
    {
        TwoWire *i2c;
    
    public:
        void begin(TwoWire* i2c)
        {
            this->i2c = i2c;
        }
    
        void reset()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x09);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t reset = i2c->read();
            reset |= 0b01000000;
            i2c->beginTransmission(0x6B);
            i2c->write(0x09);
            i2c->write(reset);
            i2c->endTransmission();
        }
    
        void setWatchdog(BQ25798Watchdog wdSettings)
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x10);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t wdt = i2c->read();
            wdt &= 0b11111000;
            wdt |= wdSettings;
            i2c->beginTransmission(0x6B);
            i2c->write(0x10);
            i2c->write(wdt);
            i2c->endTransmission();
        }
    
        void resetWatchdog()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x10);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t wdt = i2c->read();
            wdt |= 0b00001000;
            i2c->beginTransmission(0x6B);
            i2c->write(0x10);
            i2c->write(wdt);
            i2c->endTransmission();
        }
    
        void setADC(BQ25798ADC adcSettings)
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x2e);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t adc = i2c->read();
            adc &= 0b01001111;
            if (adcSettings != ADC_DISABLED)
            {
                adc |= adcSettings << 4;
                adc |= 0b10000000;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x2e);
            i2c->write(adc);
            i2c->endTransmission();
        }
    
        float getChargeVoltage() {
            i2c->beginTransmission(0x6B);
            i2c->write(0x01);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t reg = ((uint16_t)i2c->read() << 8) | i2c->read();
            return reg / 100.0;
        }
    
        void setChargeVoltage(float voltage) {
            uint16_t reg = voltage * 100;
            i2c->beginTransmission(0x6B);
            i2c->write(0x01);
            i2c->write((uint8_t)(reg >> 8));
            i2c->write((uint8_t)(reg & 0xff));
            i2c->endTransmission();
        }
    
        float getInputVoltage()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x35);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t vbus_mv = ((uint16_t)i2c->read() << 8) | i2c->read();
            return vbus_mv / 1000.0;
        }
    
        float getBatteryVoltage()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x3b);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t vbat_mv = ((uint16_t)i2c->read() << 8) | i2c->read();
            return vbat_mv / 1000.0;
        }
    
        float getChargeCurrent()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x33);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t chgcurrent = ((uint16_t)i2c->read() << 8) | i2c->read();
            int16_t chgcurrent_signed = static_cast<int16_t>(chgcurrent);
            return chgcurrent_signed / 1000.0;
        }
    
        float getInputCurrent() {
            i2c->beginTransmission(0x6B);
            i2c->write(0x31);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t chgcurrent = ((uint16_t)i2c->read() << 8) | i2c->read();
            int16_t chgcurrent_signed = static_cast<int16_t>(chgcurrent);
            return chgcurrent_signed / 1000.0;
        }
    
        float getDieTemperature()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x41);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t tdie = ((uint16_t)i2c->read() << 8) | i2c->read();
            int16_t tdie_signed = static_cast<int16_t>(tdie);
            return tdie_signed * 0.5;
        }
    
        void disableTS(bool disable)
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x18);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t tsctrl = i2c->read();
            if (disable)
                tsctrl |= 1;
            else
                tsctrl &= 0b11111110;
            i2c->beginTransmission(0x6B);
            i2c->write(0x18);
            i2c->write(tsctrl);
            i2c->endTransmission();
        }
    
        void setChargeCurrentLimit(float current) {
            uint16_t chgcurrent = current * 100; //10mA resolution
    
            if(chgcurrent > 500) { //5A absolute max
                chgcurrent = 500;
            }
            if(chgcurrent < 5) { // 50mA minimum 
                chgcurrent = 5;
            }
    
            i2c->beginTransmission(0x6B);
            i2c->write(0x03);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t chgreg = ((uint16_t)i2c->read() << 8) | i2c->read();
            chgreg &= 0b1111111000000000;
            chgreg |= chgcurrent;
            i2c->beginTransmission(0x6B);
            i2c->write(0x03);
            i2c->write((uint8_t)(chgreg >> 8));
            i2c->write((uint8_t)(chgreg & 0xff));
            i2c->endTransmission();
        }
    
        float getChargeCurrentLimit() {
            i2c->beginTransmission(0x6B);
            i2c->write(0x03);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t chgcurrent = ((uint16_t)i2c->read() << 8) | i2c->read();
            return chgcurrent / 100.0;
        }
    
        void setInputCurrentLimit(float current) {
            int chgcurrent = current * 100;
    
            if(chgcurrent > 330) { //3.3A absolute max
                chgcurrent = 330;
            }
            if (chgcurrent < 10) // 100mA minimum
            {
                chgcurrent = 10;
            }
            
            i2c->beginTransmission(0x6B);
            i2c->write(0x06);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t chgreg = ((uint16_t)i2c->read() << 8) | i2c->read();
            chgreg &= 0b1111111000000000;
            chgreg |= chgcurrent;
            i2c->beginTransmission(0x6B);
            i2c->write(0x06);
            i2c->write((uint8_t)(chgreg >> 8));
            i2c->write((uint8_t)(chgreg & 0xff));
            i2c->endTransmission();
        } 
    
        float getInputCurrentLimit() {
            i2c->beginTransmission(0x6B);
            i2c->write(0x06);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 2);
            uint16_t chgcurrent = ((uint16_t)i2c->read() << 8) | i2c->read();
            return chgcurrent / 100.0;
        }
    
        void enableChargingSafetyTimers(bool enable) {
            i2c->beginTransmission(0x6B);
            i2c->write(0x0E);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t chgctrl = i2c->read();
            if(enable) {
                chgctrl |= 0b00111000;
            }
            else {
                chgctrl &= 0b11000111;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x0E);
            i2c->write(chgctrl);
            i2c->endTransmission();
        }
    
        void enableTermination(bool enable) {
            i2c->beginTransmission(0x6B);
            i2c->write(0x0F);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t chgctrl = i2c->read();
            if(enable) {
                chgctrl |= 0b00000010;
            }
            else {
                chgctrl &= 0b11111101;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x0F);
            i2c->write(chgctrl);
            i2c->endTransmission();
        }
    
        void setPrechargeCurrent(float current) {
            uint8_t current_reg = current / 0.04;
            if(current_reg > 50) {
                current_reg = 50;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x08);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t chgctrl = i2c->read();
            chgctrl &= 0b11000000;
            chgctrl |= current_reg;
            i2c->beginTransmission(0x6B);
            i2c->write(0x08);
            i2c->write(chgctrl);
            i2c->endTransmission();
        }
    
        void forceMeasureVINDPM() {
            i2c->beginTransmission(0x6B);
            i2c->write(0x13);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t chgctrl = i2c->read();
            chgctrl |= 0b00000001;
            i2c->beginTransmission(0x6B);
            i2c->write(0x13);
            i2c->write(chgctrl);
            i2c->endTransmission();
        }
    
        void setVINDPMVoltage(float voltage) {
            uint8_t voltage_reg = voltage * 10;
            if(voltage_reg > 220) {
                voltage_reg = 220;
            }
            if(voltage_reg < 36) {
                voltage_reg = 36;
            }
    
            i2c->beginTransmission(0x6B);
            i2c->write(0x5);
            i2c->write(voltage_reg);
            i2c->endTransmission();
        }
    
        void enableMPPT(bool enable) {
            i2c->beginTransmission(0x6B);
            i2c->write(0x15);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t mppt = i2c->read();
            if(enable) {
                mppt |= 0b00000001;
            }
            else {
                mppt &= 0b11111110;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x15);
            i2c->write(mppt);
            i2c->endTransmission();
        }
    
        void disableChargerUSBDetection(bool disable) {
            i2c->beginTransmission(0x6B);
            i2c->write(0x11);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t chgctrl = i2c->read();
            if(disable) {
                chgctrl &= 0b10111111;
            }
            else {
                chgctrl |= 0b01000000;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x11);
            i2c->write(chgctrl);
            i2c->endTransmission();
        }
    
        void configureThermals(BQ25798ThermalRegulation regulation, BQ25798ThermalShutdown shutdown) {
            uint8_t _regulation = regulation << 6;
            uint8_t _shutdown = shutdown << 4;
    
            i2c->beginTransmission(0x6B);
            i2c->write(0x16);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t thermal = i2c->read();
            thermal &= 0b00001111;
            thermal |= (_regulation | _shutdown);
            i2c->beginTransmission(0x6B);
            i2c->write(0x16);
            i2c->write(thermal);
            i2c->endTransmission();
        }
    
        void configureMPPT(BQ25798MPPTVOCRatio ratio, BQ25798MPPTVOCDelay delay, BQ25798MPPTVOCInterval interval) {
            uint8_t _interval = interval << 1;
            uint8_t _delay = delay << 3;
            uint8_t _ratio = ratio << 5;
    
            i2c->beginTransmission(0x6B);
            i2c->write(0x15);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t mppt = i2c->read();
            mppt &= 0b00000001;
            mppt |= (_interval | _delay | _ratio);
            i2c->beginTransmission(0x6B);
            i2c->write(0x15);
            i2c->write(mppt);
        }
    
        void disableCharging(bool disable) {
            i2c->beginTransmission(0x6B);
            i2c->write(0x0F);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t chgctrl = i2c->read();
            if(disable) {
                chgctrl &= 0b11011111;
            }
            else {
                chgctrl |= 0b00100000;
            }
            i2c->beginTransmission(0x6B);
            i2c->write(0x0F);
            i2c->write(chgctrl);
            i2c->endTransmission();
        }
    
        BQ25798Status0 getStatus0()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x1b);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t status0 = i2c->read();
            BQ25798Status0 status;
            status.raw = status0;
            return status;
        }
    
        BQ25798Status1 getStatus1()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x1c);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t status1 = i2c->read();
            BQ25798Status1 status;
            status.raw = status1;
            return status;
        }
    
        BQ25798Status2 getStatus2()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x1d);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t status2 = i2c->read();
            BQ25798Status2 status;
            status.raw = status2;
            return status;
        }
    
        BQ25798Status3 getStatus3()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x1e);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t status3 = i2c->read();
            BQ25798Status3 status;
            status.raw = status3;
            return status;
        }
    
        BQ25798Status4 getStatus4()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x1f);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t status4 = i2c->read();
            BQ25798Status4 status;
            status.raw = status4;
            return status;
        }
    
        BQ25798Fault0 getFault0()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x20);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t fault0 = i2c->read();
            BQ25798Fault0 fault;
            fault.raw = fault0;
            return fault;
        }
    
        BQ25798Fault1 getFault1()
        {
            i2c->beginTransmission(0x6B);
            i2c->write(0x21);
            i2c->endTransmission();
            i2c->requestFrom(0x6B, 1);
            uint8_t fault1 = i2c->read();
            BQ25798Fault1 fault;
            fault.raw = fault1;
            return fault;
        }
    
        void printStatus0(BQ25798Status0 status0, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("IINDPM_STAT:          ");
            Serial.println(status0.IINDPM_STAT ? "In IINDPM regulation or IOTG regulation" : "Normal");
            Serial.print(pre);
            Serial.print("VINDPM_STAT:          ");
            Serial.println(status0.VINDPM_STAT ? "In VINDPM regulation or VOTG regulation" : "Normal");
            Serial.print(pre);
            Serial.print("WD_STAT:              ");
            Serial.println(status0.WD_STAT ? "WD timer expired" : "Normal");
            Serial.print(pre);
            Serial.print("PG_STAT:              ");
            Serial.println(status0.PG_STAT ? "Power good" : "NOT in power good status");
            Serial.print(pre);
            Serial.print("AC2_PRESENT_STAT:     ");
            Serial.println(status0.AC2_PRESENT_STAT ? "VAC2 present" : "VAC2 NOT present");
            Serial.print(pre);
            Serial.print("AC1_PRESENT_STAT:     ");
            Serial.println(status0.AC1_PRESENT_STAT ? "VAC1 present" : "VAC1 NOT present");
            Serial.print(pre);
            Serial.print("VBUS_PRESENT_STAT:    ");
            Serial.println(status0.VBUS_PRESENT_STAT ? "VBUS present" : "VBUS NOT present");
        }
    
        void printStatus1(BQ25798Status1 status1, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("CHG_STAT:             ");
            switch(status1.CHG_STAT) {
                case 0:
                    Serial.println("Not Charging");
                    break;
                case 1:
                    Serial.println("Trickle Charge");
                    break;
                case 2:
                    Serial.println("Pre-charge");
                    break;
                case 3:
                    Serial.println("Fast charge (CC mode)");
                    break;
                case 4:
                    Serial.println("Taper Charge (CV mode)");
                    break;
                case 6:
                    Serial.println("Top-off Timer Active Charging");
                    break;
                case 7:
                    Serial.println("Charge Termination Done");
                    break;
                default:
                    Serial.println("Reserved");
                    break;
            }
            Serial.print(pre);
            Serial.print("VBUS_STAT:            ");
            switch(status1.VBUS_STAT) {
                case 0:
                    Serial.println("No Input or BHOT or BCOLD in OTG mode");
                    break;
                case 1:
                    Serial.println("USB SDP (500mA)");
                    break;
                case 2:
                    Serial.println("USB CDP (1.5A)");
                    break;
                case 3:
                    Serial.println("USB DCP (3.25A)");
                    break;
                case 4:
                    Serial.println("Adjustable High Voltage DCP (HVDCP) (1.5A)");
                    break;
                case 5:
                    Serial.println("Unknown adaptor (3A)");
                    break;
                case 6:
                    Serial.println("Non-Standard Adapter (1A/2A/2.1A/2.4A)");
                    break;
                case 7:
                    Serial.println("In OTG mode");
                    break;
                case 8:
                    Serial.println("Not qualified adaptor");
                    break;
                case 11:
                    Serial.println("Device directly powered from VBUS");
                    break;
                case 12:
                    Serial.println("Backup Mode");
                    break;
                default:
                    Serial.println("Reserved");
                    break;
            }
            Serial.print(pre);
            Serial.print("BC1_2_DONE_STAT:      ");
            Serial.println(status1.BC1_2_DONE_STAT ? "BC1.2 or non-standard detection complete" : "BC1.2 or non-standard detection NOT complete");
        }
    
        void printStatus2(BQ25798Status2 status2, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("ICO_STAT:             ");
            switch(status2.ICO_STAT) {
                case 0:
                    Serial.println("ICO disabled");
                    break;
                case 1:
                    Serial.println("ICO optimization in progress");
                    break;
                case 2:
                    Serial.println("Maximum input current detected");
                    break;
                default:
                    Serial.println("Reserved");
                    break;
            }
            Serial.print(pre);
            Serial.print("TREG_STAT:            ");
            Serial.println(status2.TREG_STAT ? "Device in thermal regulation" : "Normal");
            Serial.print(pre);
            Serial.print("DPDM_STAT:            ");
            Serial.println(status2.DPDM_STAT ? "The D+/D- detection is ongoing" : "The D+/D- detection is NOT started yet, or the detection is done");
            Serial.print(pre);
            Serial.print("VBAT_PRESENT_STAT:    ");
            Serial.println(status2.VBAT_PRESENT_STAT ? "VBAT present" : "VBAT NOT present");
        }
    
        void printStatus3(BQ25798Status3 status3, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("ACRB2_STAT:           ");
            Serial.println(status3.ACRB2_STAT ? "ACFET2-RBFET2 is placed" : "ACFET2-RBFET2 is NOT placed");
            Serial.print(pre);
            Serial.print("ACRB1_STAT:           ");
            Serial.println(status3.ACRB1_STAT ? "ACFET1-RBFET1 is placed" : "ACFET1-RBFET1 is NOT placed");
            Serial.print(pre);
            Serial.print("ADC_DONE_STAT:        ");
            Serial.println(status3.ADC_DONE_STAT ? "Conversion complete" : "Conversion NOT complete");
            Serial.print(pre);
            Serial.print("VSYS_STAT:            ");
            Serial.println(status3.VSYS_STAT ? "In VSYSMIN regulation (VBAT < VSYSMIN)" : "Not in VSYSMIN regulation (VBAT > VSYSMIN)");
            Serial.print(pre);
            Serial.print("CHG_TMR_STAT:         ");
            Serial.println(status3.CHG_TMR_STAT ? "Safety timer expired" : "Normal");
            Serial.print(pre);
            Serial.print("TRICHG_TMR_STAT:      ");
            Serial.println(status3.TRICHG_TMR_STAT ? "Safety timer expired" : "Normal");
            Serial.print(pre);
            Serial.print("PRECHG_TMR_STAT:      ");
            Serial.println(status3.PRECHG_TMR_STAT ? "Safety timer expired" : "Normal");
        }
    
        void printStatus4(BQ25798Status4 status4, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("VBATOTG_LOW_STAT:     ");
            Serial.println(status4.VBATOTG_LOW_STAT ? "The battery volage is too low to enable the OTG operation" : "The battery volage is high enough to enable the OTG operation");
            Serial.print(pre);
            Serial.print("TS_COLD_STAT:         ");
            Serial.println(status4.TS_COLD_STAT ? "TS status is in cold range" : "TS status is NOT in cold range");
            Serial.print(pre);
            Serial.print("TS_COOL_STAT:         ");
            Serial.println(status4.TS_COOL_STAT ? "TS status is in cool range" : "TS status is NOT in cool range");
            Serial.print(pre);
            Serial.print("TS_WARM_STAT:         ");
            Serial.println(status4.TS_WARM_STAT ? "TS status is in warm range" : "TS status is NOT in warm range");
            Serial.print(pre);
            Serial.print("TS_HOT_STAT:          ");
            Serial.println(status4.TS_HOT_STAT ? "TS status is in hot range" : "TS status is NOT in hot range");
        }
    
        void printFault0(BQ25798Fault0 fault0, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("IBAT_REG_STAT:        ");
            Serial.println(fault0.IBAT_REG_STAT ? "Device in battery discharging current regulation" : "Normal");
            Serial.print(pre);
            Serial.print("VBUS_OVP_STAT:        ");
            Serial.println(fault0.VBUS_OVP_STAT ? "Device in over voltage protection" : "Normal");
            Serial.print(pre);
            Serial.print("VBAT_OVP_STAT:        ");
            Serial.println(fault0.VBAT_OVP_STAT ? "Device in over voltage protection" : "Normal");
            Serial.print(pre);
            Serial.print("IBUS_OCP_STAT:        ");
            Serial.println(fault0.IBUS_OCP_STAT ? "Device in over current protection" : "Normal");
            Serial.print(pre);
            Serial.print("IBAT_OCP_STAT:        ");
            Serial.println(fault0.IBAT_OCP_STAT ? "Device in over current protection" : "Normal");
            Serial.print(pre);
            Serial.print("CONV_OCP_STAT:        ");
            Serial.println(fault0.CONV_OCP_STAT ? "Device in over current protection" : "Normal");
            Serial.print(pre);
            Serial.print("VAC2_OVP_STAT:        ");
            Serial.println(fault0.VAC2_OVP_STAT ? "Device in over voltage protection" : "Normal");
            Serial.print(pre);
            Serial.print("VAC1_OVP_STAT:        ");
            Serial.println(fault0.VAC1_OVP_STAT ? "Device in over voltage protection" : "Normal");
        }
    
        void printFault1(BQ25798Fault1 fault1, char* pre = nullptr) {
            Serial.print(pre);
            Serial.print("VSYS_SHORT_STAT:      ");
            Serial.println(fault1.VSYS_SHORT_STAT ? "Device in SYS short circuit protection" : "Normal");
            Serial.print(pre);
            Serial.print("VSYS_OVP_STAT:        ");
            Serial.println(fault1.VSYS_OVP_STAT ? "Device in SYS over voltage protection" : "Normal");
            Serial.print(pre);
            Serial.print("OTG_OVP_STAT:         ");
            Serial.println(fault1.OTG_OVP_STAT ? "Device in OTG over voltage" : "Normal");
            Serial.print(pre);
            Serial.print("OTG_UVP_STAT:         ");
            Serial.println(fault1.OTG_UVP_STAT ? "Device in OTG under voltage" : "Normal");
            Serial.print(pre);
            Serial.print("TSHUT_STAT:           ");
            Serial.println(fault1.TSHUT_STAT ? "Device in thermal shutdown protection" : "Normal");
        }
    
        void printAllStatus(char* pre = nullptr) {
            BQ25798Status0 status0 = getStatus0();
            BQ25798Status1 status1 = getStatus1();
            BQ25798Status2 status2 = getStatus2();
            BQ25798Status3 status3 = getStatus3();
            BQ25798Status4 status4 = getStatus4();
            BQ25798Fault0 fault0 = getFault0();
            BQ25798Fault1 fault1 = getFault1();
            Serial.print(pre);
            Serial.println("==== STATUS ====");
            printStatus0(status0, pre);
            printStatus1(status1, pre);
            printStatus2(status2, pre);
            printStatus3(status3, pre);
            printStatus4(status4, pre);
            Serial.print(pre);
            Serial.println("==== FAULT ====");
            printFault0(fault0, pre);
            printFault1(fault1, pre);
        }
    };


    Here's a minimal usage example:
    #include <Wire.h>
    #include "bq25798.h"
    
    BQ25798 bq;
    
    void setup() {
        bq.begin(&Wire); // Initialize library
        bq.reset(); // Reset BQ internal registers to start from a "clean" state
        bq.setADC(BQ25798ADC::ADC_15BIT); // Enable ADC in 15-bit mode
        bq.disableTS(true); // Disable temperature sensing, if no TS is present, charging won't start without this
    
        // Optional stuff, check datasheet for default values and explanations
        bq.setChargeCurrentLimit(2.2); // Max current into battery = 2.2A
        bq.setInputCurrentLimit(2); // Max current into charger = 2A
        bq.setChargeVoltage(16.6); // Override default target charge voltage. For example, when configured for a 4S battery("9.3.2 PROG Pin Configuration" on page 25 in datasheet), this would be 16.8V by default.
        bq.enableChargingSafetyTimers(false); // Check datasheet for details("9.3.9.4 Charging Safety Timer", page 44). If the charger is powered for a long time, the charging safety timer will stop charging after a while.
        bq.enableTermination(false); // Disable charge termination
        bq.setWatchdog(BQ25798Watchdog::WD_2S); // 2 second watchdog, if expires, BQ is reset to default settings. If no thermistor is present, this means charging is stopped.
        bq.configureMPPT(BQ25798MPPTVOCRatio::VOCPERCENT_0_8125, BQ25798MPPTVOCDelay::VOCDELAY_2S, BQ25798MPPTVOCInterval::VOCINTERVAL_2M); // Configure MPPT with 81.25% Voc ratio, 2 second delay before measuring Voc, measure Voc every 2 minutes.
        bq.enableMPPT(true);
    }
    
    void loop() {
        bq.printAllStatus();
    
        float inputVoltage = bq.getInputVoltage();
        float outputVoltage = bq.getBatteryVoltage();
        float inputCurrent = bq.getInputCurrent();
        float outputCurrent = bq.getChargeCurrent();
        Serial.print("Input voltage: ");
        Serial.print(inputVoltage);
        Serial.println("V");
        Serial.print("Output voltage: ");
        Serial.print(outputVoltage);
        Serial.println("V");
        Serial.print("Input current: ");
        Serial.print(inputCurrent);
        Serial.println("A");
        Serial.print("Output current: ");
        Serial.print(outputCurrent);
        Serial.println("A\n");
    
        bq.resetWatchdog();
    } 


    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • PhilippGrab
    PhilippGrab 2 months ago

    Awesome Project!

    I would like to ask if got around to publishing the Lib for the BQ25798 yet? I am an Electronics Engineer, however Software developement is not my forte. I would be happy for just the "not clean" Lib, if you havent got the time yet. If got a few Projects lined up, where this Chip would be absolutely perfect. Cheers!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB 10 months ago

    Ah, but the adventure was priceless.

    Nice fix.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • vmate
    vmate 10 months ago in reply to scottiebabe

    That makes sense, thanks for the info! It's still a mystery to me why they didn't make some of the pins larger though. In the PDF, the flip chip package has really long pins to get the heat out, so I don't know why they didn't to the same here.

    Any ideas why anyone would want to use the flip chip packaged part instead of the large thermal pad equipped one, when both are available?

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • scottiebabe
    scottiebabe 10 months ago

    Wonderful project & write up. There is a good chance the battery charger is a flipchip QFN (FC-QFN or Hotrod QFN)

    https://www.ti.com/lit/an/slvaee1/slvaee1.pdf?ts=1724346665062

    A low impedance thermal path is achieved through the pins as each pad on the die is soldered to the leadframe as opposed to wire bonded. 

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube