Skip to content

Battery gauge & fans

Prerequisites: the overlays and the config.txt block from the manual setup page - they already include the stc3117-gauge and i2c-fan lines used here.

The battery is measured by an STC3117 fuel gauge (I2C address 0x70) on the compute module’s SDA0/SCL0 pair. It sits behind the Pi / ESP32-S3 I2C multiplexer, which hands it to the Pi at power-on (while the Pi owns the display).

Raspberry Pi OS does not build CONFIG_FUEL_GAUGE_STC3117, so TypixDeck OS ships the driver as a DKMS module - it is rebuilt automatically whenever a kernel and its headers are installed.

Terminal window
sudo apt install -y dkms linux-headers-rpi-v8 linux-headers-rpi-2712
# (the dwc2fix kernel from the USB audio page brings its own headers)
[ -d ~/typixdeck-pi-gen ] || git clone --depth 1 -b typixdeck https://github.com/TypixNode/pi-gen.git ~/typixdeck-pi-gen
SRC=/usr/src/stc3117-fuel-gauge-6.18
sudo mkdir -p $SRC
sudo cp ~/typixdeck-pi-gen/typixdeck-stage/03-battery-dkms/files/{stc3117_fuel_gauge.c,Makefile,dkms.conf} $SRC/
sudo dkms add -m stc3117-fuel-gauge -v 6.18
sudo dkms install -m stc3117-fuel-gauge -v 6.18
sudo reboot

Check:

Terminal window
dmesg | grep stc3117
cat /sys/class/power_supply/stc3117-battery/{capacity,voltage_now,current_now,status}
upower -d | grep -E 'percentage|state'

UPower picks the battery up by itself, so the battery indicator of every desktop (labwc panel, Phosh, Plasma) works without further setup.

stc3117-gauge describes the board:

Property Value Why
shunt-resistor-micro-ohms 10000 the 10 mΩ current-sense resistor
charge-full-design-microamp-hours 2400000 604070 Li-Po, ~2400 mAh
voltage-min/max-design-microvolt 3.0 V / 4.2 V
factory-internal-resistance-micro-ohms 140000 pack resistance as the gauge sees it (cell + protection board + wiring), measured with a CPU load step; the driver’s 200 mΩ default seeded the charge ~15 points low

Bus: on CM4 the default target is i2c_csi_dsi (GPIO44/45, i2c-10); on CM5 pass the i2c_csi_dsi0 parameter (RP1, GPIO38/39) - which is why the config.txt lines differ. Do not point the CM4 at i2c_csi_dsi0: on the CM4 that is GPIO0/1, which belong to the DPI screen (pin gpio0 already requested). Parameters capacity-uah, shunt-uohm and rint-uohm override the values for a different battery.

Do not load the mainline stc3117_fuel_gauge as-is on a TypixDeck. It writes the 16 × 16-bit OCV table as 16 bytes and corrupts it, reports tenths of a percent as the percentage (4.6 % shows as 46 %), reads current unsigned with the wrong sign convention, and resets a gauge the ESP32-S3 has already started. The TypixDeck copy fixes those and adds:

  • Seeding once per battery, shared with the ESP32-S3. The gauge’s state is shared by both processors, so they agree through a marker in the gauge’s own battery-backed RAM: whichever gets the bus first seeds the charge from the measured voltage and sets the mark; the other only reads. Removing the battery clears the RAM and re-arms the seed. Without it a freshly installed system could show 3-6 % on a full battery.
  • A 4.2 V discharge curve for the 604070 pack, written together with the matching state-of-charge breakpoints (mainline’s curve belongs to a 4.35 V cell and never reaches 100 % here).
  • Re-seeding only on consistent evidence - voltage and current are converted in different cycles, so a single reading during a load change is not trusted.
  • charge_now / charge_full for panel plugins that compute the percentage themselves, and the battery stays present while the multiplexer hands the gauge to the ESP32-S3.

The i2c-fan,emc2301 lines in the config.txt block bind Raspberry Pi OS’s own i2c-fan overlay to the board’s EMC2301 on the same SDA0/SCL0 pair. Without it the controller stays at its power-on 100 % duty and no fan sensor exists.

  • The overlay enables the bus itself - do not add dtparam=i2c_vc=on alongside it.
  • If you tune it, keep minpwm ≤ 51 on kernel 6.12 and newer: a higher value makes the driver refuse the first cooling state and the fan probe fails silently. midtemp, maxtemp and maxpwm are safe to change.

The CM5 also drives a Pi 5-style 4-pin PWM fan (pwmfan). The stock curve is timid for a closed case - 29 % from 50 °C - so TypixDeck OS keeps the trip temperatures and raises the duty at every step:

Trip Stock PWM TypixDeck PWM Measured
50 °C 75 130 3.6k → 6.5k RPM
60 °C 125 180
67.5 °C 175 220
75 °C 250 255 10.0k RPM at full
[cm5]
dtparam=cooling_fan=on
dtparam=fan_temp0_speed=130
dtparam=fan_temp1_speed=180
dtparam=fan_temp2_speed=220
dtparam=fan_temp3_speed=255

These names are defined by the CM5 device trees themselves (fan_temp0..3, _hyst, _speed), so they are safe; a misspelled dtparam is the classic way to end up with a board that boots without its fan. To check what is running:

Terminal window
od -An -t u1 /proc/device-tree/cooling_fan/cooling-levels # PWM per state
for d in /sys/class/hwmon/hwmon*; do [ -r $d/fan1_input ] && echo "$(cat $d/name) $(cat $d/fan1_input) rpm pwm=$(cat $d/pwm1)"; done

The hwmon numbers change between boots - look fans up by name, never by hwmonN.