Battery Monitoring with Zephyr’s Fuel Gauge Subsystem

This week I went on a fascinating odyssey into Zephyr’s fuel gauge subsystem. This doesn’t refer to gasoline (or petrol as my more refined colleagues would say). It instead deals with the ubiquitous Lithium rechargeable cell. There’s a lot that goes into monitoring and charging these batteries, so it’s not surprising that the silicon industry has a class of chips known as fuel gauge ICs. They automatically handle tasks like calculating percentage of battery capacity, run-time until empty, charge-time until full, current/voltage measurement, and battery cut-off. If you haven’t looked at these chips, you should!

Batteries are the future and Zephyr is ready for it with a number of drivers already in-tree. The drivers are pretty user-friendly, but what’s built into Zephyr does fall into two different driver categories. Let’s get plugged in and see what the fuel gauge is all about.

Is it a Fuel Gauge or a Sensor?

I’m not embarrassed to admit I lost a bunch of time trying to figure out why my Maxim max17262 fuel gauge IC has Zephyr support but doesn’t work with the fuel gauge subsystem. It turns out that some fuel gauge ICs have sensor drivers, while others have fuel gauge drivers. Don’t worry, you can get nearly the same data from either, but you need to use the one supported by your device.

How can you tell what kind of support exists for your chip? Head over to the Zephyr bindings index and look it up. On the face of it you can see several of the max17xxx chips have zephyr support.

Maxim max17xxx chips listed in the Zephyr bindings indexBut when we click on the links and drill down to the details for each of these drivers, we find that the max17048 has fuel gauge subsystem support while the max17262 has sensor subsystem support.

Maxim max17048 fuel gauge supportMaxim max17262 sensor subsystem supportDifferent drivers have Kconfig and API names that are… different. But the approach to working with these chips will be largely the same. You get an instance of the chip out of devicetree, run a fetch command to populate a struct with values read from the chip, then utilize those values.

Using the Fuel Gauge Subsystem

I don’t have a chip supported by the fuel gauge subsystem but I did take a thorough look into the drivers, including the sample app for the max17048. This driver has very few devicetree options available:

max17048:max17048@36 {
    compatible = "maxim,max17048";
    status = "ok";
    reg = <0x36 >;
};

Enable the library using Kconfig:

CONFIG_FUEL_GAUGE=y

Access the device readings in c using a typical Zephyr pattern:

const struct device *const dev = DEVICE_DT_GET_ANY(maxim_max17048);

union fuel_gauge_prop_val batt_val;
int ret = fuel_gauge_get_prop(dev, FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE, &batt_val);

Your battery percentage will now be stored in batt_val.relative_state_of_charge. The sample code shows four different properties being read from the max17048 (which is all this particular driver support). The subsystem defines numerous properties, accessible depending on the driver for your specific chip.

Using the Sensor Subsystem

The Golioth Elixir has a max17262 fuel gauge IC on the board which has Zephyr support via the sensory subsystem. Zephyr does include a sample app for reading from this chip. Despite not being directly included in the fuel gauge subsystem, this chip has for reading 14 properties (significantly more than the first driver we looked at).

The devicetree binding for this chip includes a number of useful properties so that device readings are properly associated with the physical parameters of battery you are using.

max17262@36 {
    compatible = "maxim,max17262";
    reg = <0x36>;
    design-voltage = <4200>;
    desired-voltage = <3700>;
    desired-charging-current = <800>;
    design-cap = <850>;
    empty-voltage = <3300>;
    recovery-voltage = <3880>;
    charge-voltage = <4200>;
    status = "okay";
};

Enable the library using Kconfig:

CONFIG_SENSOR=y

Access the device readings in c using a typical Zephyr pattern:

static const struct device *const dev = DEVICE_DT_GET_ONE(maxim_max17262);

struct sensor_value batt_pct;
sensor_sample_fetch(dev); /* gather all sensor values */
sensor_channel_get(dev, SENSOR_CHAN_GAUGE_STATE_OF_CHARGE, &batt_pct);

Your battery percentage will now be stored in batt_pct.  I’ll leave it up to you to explore all the other properties you can read form this device.

Useful Chips with Useful Driver

Adding a fuel gauge IC to your battery-powered designs offloads almost all effort when it comes to taking reliable battery readings and estimating charge and drain times. Zephyr’s support for these values makes using them a snap when the driver is already in-tree. But even if there is no existing driver, the infrastructure greatly lessens the burden of adding your own.

Mike Szczys
Mike Szczys
Mike is a Firmware Engineer at Golioth. His deep love of microcontrollers began in the early 2000s, growing from the desire to make more of the BEAM robotics he was building. During his 12 years at Hackaday (eight of them as Editor in Chief), he had a front-row seat for the growth of the industry, and was active in developing a number of custom electronic conference badges. When he's not reading data sheets he's busy as an orchestra musician in Madison, Wisconsin.

Post Comments

No comments yet! Start the discussion at forum.golioth.io

More from this author

Related posts

spot_img

Latest posts

Golioth Firmware SDK v0.18.0

Golioth released Firmware SDK v0.18.0 which pulls in the recent changes from upstream Zephyr, nRF Connect SDK, and ESP-IDF repositories. We are also introducing new gateway support, adding new supported boards, and improving blockwise transfers.

New Pipelines Data Destination: LightDB State

A new Pipelines data destination for LightDB State is now generally available for Golioth users and will enable a range of new bidirectional interactions with 3rd party services.

How to Query LightDB Stream Data the Right Way (POST vs GET)

Golioth's REST API is a power way to query data that has been sent to the cloud. Marko explains how he approaches the tradeoffs of POST vs GET calls to the API and how it impacts the resulting data.

Want to stay up to date with the latest news?

Subscribe to our newsletter and get updates every 2 weeks. Follow the latest blogs and industry trends.