Note: In the course of all this I created an improved Arduino I2C bus scanner. Check it out!
First, a thank-you to Pierce from Logos Electromechanical who clued me in on what I was doing wrong with the backpack.
It turns out there are two common ways to detect an I2C device: address it and see if it ACKs (quick mode), or address it and try to read a byte from it (read mode). Some devices that aren’t set up to write don’t like to be read from and will cause problems if you try to do that (in this case, SDA gets pulled low and stays low!) I thought it was a bug but no, it’s actually how this device is supposed to work.
I selected an address for my backpack of 0x58. It turns out that i2cdetect has some intelligence when it comes to its default behavior – it will decide based on the address how best to perform its test of that address. If the device address is in the ranges {0x30:0x37,0x50:0x5f}, it will use read mode. Outside those ranges, it will use quick mode. See the i2cdetect.c code for how this was implemented in Linux.
So I chose… poorly – at least when it came to the address for the backpack (and keep in mind that the default address of this device is 0x7F, which is invalid for I2C, so you MUST select a valid address via solder bridging the appropriate pads!) Since the high (0x40) bit is already “1” on this device, you can choose an address within the valid range of 0x40:0x77… I suggest something outside the 0x50:0x5F range though, to avoid accidentally triggering this (otherwise, be careful to use “-q” with i2cdetect). You can quickly tell when you screw up and try to read from this device: everything else will drop from your I2C bus and detect will act like every I2C address is in use by a device! (Unplugging the backpack from the circuit and from its power source for a moment will reset its state.)
You can read more about the I2C protocol here and here, learn about special addressing modes here, and see the specs here.
When using the Raspberry Pi model B note the I2C bus on the main header is numbered 1 (older boards have bus 0 on that header).