Saturday, May 22, 2021

Rechargeable Lithium-Ion battery caddy compatible with Lego® Powered Up!® Hub 88009

9V battery caddy for powered up hub
with USB-rechargeable Li-ion battery

The Lego® Powered Up!® Hub 88009 is found in the Powered Up train series amongst other parts. 

It's a decent part as far as it goes.... except for the batteries. 6xAAA cells, and it doesn't like NiMH rechargeables much.

So I designed a replacement 3D-printable battery caddy that adapts it to a regular 9V form factor. You can then use with a micro-USB Li-ion rechargeable block battery. No soldering or fiddling with electronics required. See below for details.

Why was this even necessary?

Powered Up battery caddy
Hub battery caddy
Image by The Brothers Brick
For some insane reason Lego decided to make it take 6 (yes, six!) AAA batteries to supply its 9V nominal voltage. Worse, it has poor voltage regulation and starts complaining that the batteries are dying when the supply voltage drops to something like 6.5V, and cuts off entirely around 6V. This eats batteries.

Powered Up Hub 88009
Powered Up Hub 88009
The poor voltage regulation means that it prefers 1.5V alkaline dry-cell batteries. If you try to use it with NiMh rechargeable batteries (AAA HR03 / 24H), which tend to top out at 1.25V under load when fully charged, it won't run for long before it decides the batteries are "dead". If your rechargeables aren't in good condition it might not run at all.


Replacement caddy design for rechargeables

Micro-USB rechargeable 9V
battery in commonplace PP3 form factor

The battery gobbling hub annoyed me, so I fixed it to take a single 9V battery (PP3) by designing a 3D-printable replacement battery caddy that you can swap for the original one. It's easy to get micro-USB-rechargeable Lithium-Ion 9V batteries with their own internal voltage regulator, discharge limiter, current limiter, charge controller, etc, so this provides a cheap and easy conversion of your Lego Powered Up hub to a fast-recharging, long-lived power supply. 

An 8.4V or 9.6V NiMH cell should work too, but I don't have any to test.

This caddy design is keyed to prevent insertion of the battery with the wrong polarity. It has some isolating material to prevent the contacts from shorting. It fits the battery securely and neatly, and prevents it rattling around. The contacts with the hub work well. The caddy has the appropriate keying and clip notches to fit into the hub neatly.

Parts and tools required

  • One or more 9V Li-Ion battery packs. You can test with a regular alkaline or NiMH 9V though.

  • Access to a 3D printer for about 4h of print time

  • A little bit of metal plate to cut and bend for the contacts. I recommend 0.5mm copper plate but it's likely you could use brass, aluminium, steel or whatever you have to hand. Exact thickness is not critical.

You will need to 3D print the caddy. 

Download the latest STL from my github repo (at time of writing, lego_powered_up_battery_box_v5.stl).

Printing in PLA should work fine, it's not especially difficult or fiddly. Configure your slicer to print overhangs slowly if you want the clips to function well, otherwise it's just the usual quality/time trade-offs. Dimensional accuracy matters, so avoid filaments that shrink a lot or be prepared to do some trimming. I recommend printing with a brim.

Once printed and trimmed, you will need to make some contacts. Cut two 4mm x 15mm strips of suitable 0.5mm thick metal. Err on the side of narrow (<4mm) not wide. Length isn't critical.

Line up one contact as it will in the caddy, diagonally across from the +ve battery contact across to the centre caddy contact. You will see a little diagonal channel to help you. On one end of the strip, mark and clip the ends so they will sit nicely in the contact pad guide once the strip is inserted. I used side cutters. Then insert one strip into the middle (+ve) contact slot near-vertically and once inserted, turn it around until it's firmly in place.  Glue if desired.

Insert the second strip vertically into the -ve slot. Mark it where it sticks out past the top of the contact cutout. Remove it and fold it over at that point, so it doubles back on itself. Crush it flat at the fold with pliers. Reinsert it and make sure it engages with the little cutout the bottom of the caddy. Glue if desired.

Make sure both contacts fit the battery terminals.

Check the fit of the caddy in the hub box. Trim, shave or sand the keying ridges if required.


(For the contacts I actually cut a short length of copper pipe then flattened and thinned it out in a rolling press. But it's probably easier to just buy a little copper plate from a hobby shop.)

Related work

Unlike Philo's LiPo conversion this one can be done with an off-the-shelf battery that provides all required voltage regulation and charging circuits internally. Just plug in a micro-USB connector and you're done. (In any case I didn't find his conversion until I'd already completed this one - I originally intended to take the same approach he did with a separate voltage regulation circuit etc, but then found out about these handy all-in-one units).

While writing this article I also stumbled across a eurobricks post that details a simple conversion done by adapting a regular 9V battery clip to the Powered Up contacts layout. I chose not to take this approach mainly because I wanted polarity protection and a nice snug fit where the battery wouldn't rattle around.

Why would Lego design it this way?

The cutoff voltage in the hub is not completely unreasonable - when over-discharged, alkaline batteries are more prone to leaking and splitting, which can potentially be hazardous and/or damage the contacts.

But the hub has a separate battery caddy so it's pretty well protected from leakage. 

So I suspect they designed it for a 5V supply with headroom for a simple linear voltage regulator.

I'm very disappointed that Lego has not released their own rechargeable battery module for these units. It has a swappable internal caddy, and you'd think that a USB-rechargeable battery pack would be a no-brainer product for them to release.

Monday, September 21, 2020

(Failing to) reconfigure host PCI or USB devices for guest kvm libvirt passthrough at runtime: how to unbind built-in kernel drivers from devices without a reboot

I recently had an NVMe drive fault and wanted to check for a firmware update. Western Digital (was SanDisk) in their infinite wisdom only offer firmware for their drives in the form of  ... a Windows management application.

How hard can it really be to run that under Linux?

Moderately, it turns out, and that's by using a Windows VM under libvirt-managed kvm to do it.

First I needed VT-d and the host IOMMU enabled:

  • ensure VT-d was enabled in UEFI firmware (it was). Check grep -oE 'svm|vmx' /proc/cpuinfo | uniq . If there's output, it's available.
  • Run sudo virt-host-validate to check kvm. The line "Checking if IOMMU is enabled by kernel" was marked with a warning.
  • Add intel_iommu=on to my kernel command line and reboot
  • Re-check virt-host-validate, which no longer complains

Then I needed to unbind my NVMe controller and the parent bus from the host so I could pass it through. 

Obviously you can only do this if you're booted off something that won't need the PCI devices you're unbinding. In my case I'm booted from a USB3 HDD.

 Identify the bus path to the NVMe device:

$ sudo lspci -t -nnn -vv -k 
-[0000:00]-+-00.0 Intel Corporation Device [8086:9b61]
+-02.0 Intel Corporation UHD Graphics [8086:9b41]
+-04.0 Intel Corporation Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Thermal Subsystem [8086:1903]
+-08.0 Intel Corporation Xeon E3-1200 v5/v6 / E3-1500 v5 / 6th/7th/8th Gen Core Processor Gaussian Mixture Model [8086:1911]
+-12.0 Intel Corporation Comet Lake Thermal Subsytem [8086:02f9]
+-14.0 Intel Corporation Device [8086:02ed]
+-14.2 Intel Corporation Device [8086:02ef]
+-14.3 Intel Corporation Wireless-AC 9462 [8086:02f0]
+-16.0 Intel Corporation Comet Lake Management Engine Interface [8086:02e0]
+-17.0 Intel Corporation Comet Lake SATA AHCI Controller [8086:02d3]
+-1d.0-[04]----00.0 Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168]
+-1d.4-[07]----00.0 Sandisk Corp Device [15b7:5005]
+-1f.0 Intel Corporation Device [8086:0284]
+-1f.3 Intel Corporation Device [8086:02c8]
+-1f.4 Intel Corporation Device [8086:02a3]
\-1f.5 Intel Corporation Comet Lake SPI (flash) Controller [8086:02a4]

In this case it's device 15b7:5005 .

I originally tried to pass through just that device after unloading the nvme module, but it failed with errors like

vfio-pci 0000:03:00.0: not ready 65535ms after FLR; giving up

so I landed up having to unbind the parent on the bus too.

First, identify the kernel drivers used, bus IDs, and device IDs. In my case I'll be doing the NVMe SSD device 15b7:5005, the parent SATA AHCI controller 8086:02d3, and the PCIe ethernet controller 10ec:8168 that seems to be a child of the SATA controller.

Use lspci -k -d {{deviceid}} to see the kernel driver bound to each device, and any kernel module(s) registered as supporting it, e.g.:

# lspci -k -d 8086:02d3
00:17.0 SATA controller: Intel Corporation Comet Lake SATA AHCI Controller
    Subsystem: Lenovo Device 5079
    Kernel driver in use: ahci

# lspci -k -d 15b7:5005
07:00.0 Non-Volatile memory controller: Sandisk Corp Device 5005 (rev 01)
    Subsystem: Sandisk Corp Device 5005
    Kernel driver in use: nvme
    Kernel modules: nvme

If it's owned by a module you can often just unload the module to unbind it, e.g.

$ rmmod nvme

but if it's owned by a built-in driver like "ahci" is in my kernel, you can't do that. It doesn't show up in lsmod and cannot be rmmod'd.

Instead you need to use sysfs to unbind it. (You can do this for devices bound to modules too, which is handy if you need the module for something critical for the host OS).

To unbind the ahci driver from the controller on my host, for example, here's what I did:

# ls /sys/module/ahci/drivers/
# ls "/sys/module/ahci/drivers/pci:ahci/"
0000:00:17.0  bind  module  new_id  remove_id  uevent  unbind

Note that '0000:00:17.0' matches the bus address we saw in lspci? Cool. Now unbind it:

# echo '0000:00:17.0' > "/sys/module/ahci/drivers/pci:ahci/"

Verify everything's unbound now:

# lspci -k -d 8086:02d3 
00:17.0 SATA controller: Intel Corporation Comet Lake SATA AHCI Controller
    Subsystem: Lenovo Device 507
# lspci -k -d 15b7:5005
07:00.0 Non-Volatile memory controller: Sandisk Corp Device 5005 (rev 01)
    Subsystem: Sandisk Corp Device 5005
    Kernel modules: nvme

Now bind it into the vfio-pci driver with:

# modprobe vfio-pci ids=8086:02d3,15b7:5005

Nowwith a bit of luck it can be attached to a kvm so it's accessible inside the guest. 

I used virt-manager for that because libvirt's semi-documented XML-based interface makes me want to scream. Just the guest, "add hardware", "PCI Device", and pick both the NVMe controller and the parent device. I didn't bother with the Ethernet controller, didn't seem to need it.

Sadly, it still won't work:

[ 2641.079391] vfio-pci 0000:07:00.0: vfio_ecap_init: hiding ecap 0x19@0x300
[ 2641.079395] vfio-pci 0000:07:00.0: vfio_ecap_init: hiding ecap 0x1e@0x900
[ 2641.109860] pcieport 0000:00:1d.4: DPC: containment event, status:0x1f11 source:0x0000
[ 2641.109863] pcieport 0000:00:1d.4: DPC: unmasked uncorrectable error detected
[ 2641.109867] pcieport 0000:00:1d.4: AER: PCIe Bus Error: severity=Uncorrected (Non-Fatal), type=Transaction Layer, (Receiver ID)
[ 2641.109868] pcieport 0000:00:1d.4: AER:   device [8086:02b4] error status/mask=00200000/00010000
[ 2641.109869] pcieport 0000:00:1d.4: AER:    [21] ACSViol                (First)
[ 2642.319541] vfio-pci 0000:07:00.0: not ready 1023ms after FLR; waiting
[ 2643.407529] vfio-pci 0000:07:00.0: not ready 2047ms after FLR; waiting
[ 2645.519286] vfio-pci 0000:07:00.0: not ready 4095ms after FLR; waiting
[ 2650.063213] vfio-pci 0000:07:00.0: not ready 8191ms after FLR; waiting
[ 2658.767373] vfio-pci 0000:07:00.0: not ready 16383ms after FLR; waiting
[ 2675.663235] vfio-pci 0000:07:00.0: not ready 32767ms after FLR; waiting
[ 2712.526507] vfio-pci 0000:07:00.0: not ready 65535ms after FLR; giving up
[ 2713.766494] pcieport 0000:00:1d.4: AER: device recovery successful
[ 2714.435994] vfio-pci 0000:07:00.0: vfio_bar_restore: reset recovery - restoring BARs
[ 2764.090284] pcieport 0000:00:1d.4: DPC: containment event, status:0x1f15 source:0x0700
[ 2764.090286] pcieport 0000:00:1d.4: DPC: ERR_FATAL detected
[ 2764.254057] pcieport 0000:00:1d.4: AER: device recovery successful

... followed by a hang of the VM. But hey. It was worth a try.

DPC is "Downstream Port Containment" which is supposed to protect the host from failures in PCI devices by isolating them.

Since this later scrambled my host graphics and I had to force a reboot,

At least now you know how to unbind a driver from a device on the host without a reboot and without messing around with module blacklisting etc.


Tuesday, September 3, 2019

Giving the Microsoft Wireless Desktop 900 keyboard a power switch

Say whatever else you want about them, Microsoft has always made amazing peripherals hardware. I got their Microsoft Wireless Desktop 900 keyboard/mouse set, which is much the same as the Wireless Keyboard 850 in a kb/mouse bundle. It's no exception - reasonable price, great quality, does exactly what it says with no nonsense and stray LEDs. The keyboard is really flat and convenient.

Unfortunately they took the minimalism a bit too far. The keyboard has no power switch.

Friday, June 28, 2019

Fix Lenovo T460 failure to suspend (sleep) under Linux

My Lenovo T460 with Intel integrated graphics abruptly stopped suspending properly a few weeks ago and it's been driving me nuts. Today I fixed it.

TL;DR: For my specific issue lowering the integrated graphics memory portal from 512MB to 256MB in the UEFI firmware setup ("BIOS setup", F1 boot menu) resolved the issue.

Thursday, June 13, 2019

Updating Lenovo T460 BIOS (UEFI, firmware) without a CD on Linux

Some of Lenovo's newer models come with a firmware update distribution compatible with fwupd so you can just do a firmware update directly from the shell.

This is not the case for the T460, which has a bootable ISO, or a Windows installer that ... creates a bootable ISO.

I couldn't find my USB CD/DVD drive so I landed up finding a workaround.

Thursday, May 2, 2019

Nova Empire Beginners Tips

I've fallen down the rabbit hole of a p2w but still fun if you're free-to-play game called Nova Empire. Despite numerous flaws the game is entertaining and good for when you're pinned under a baby.

However, it has a bunch of traps for beginners. I'd like to share some tips. Currently as a bit of a random assortment. I'll update this occasionally.

Beware, the game is kind of addictive. I play it when I'm pinned under my cranky toddler who wants to use me as a pillow. But it's designed to get its hooks into you and get you playing more, so keep an eye on it. If you're tempted to spend money on it... well, with my software developer hat on I say "go ahead, really, do it." But with my responsible adult hat on I say "the prices are ridiculous for what you get, just be patient, it's a game". If you do want to drop some cash to support them, do it early on in the game to get to level 5 station faster and speed up overall growth.

Saturday, March 2, 2019

Adafruit Gemma, Macbook and the dim red light

My partner recently picked up an Adafruit Gemma as an intro to Arduino and embedded. It has not gone according to plan - it turns out there's a known issue where on some systems the Gemma will fail to enter the bootloader properly. The green power light comes on, but the red light stays dimly and constantly lit. If the reset button is pressed it goes out for a moment then returns.

Trying to program it anyway results in an error like

avrdude: Error: Could not find USBtiny device (0x1781/0xc9f)

The issue seems to be somewhat intermittent, too. Occasionally it works on her Macbook Air. It always works 100% of the time on my Lenovo T460 (Linux). So there's something platform related...

Not a great intro. The Gemma doesn't behave like usual Ardiuno systems - it doesn't expose a USB serial port, for example - so answers are also thinner on the ground.

But I think we found something.