I had one of those why-didn’t-I-think-of-it moments this week, reading this article about multiplexing I2C on the ESP32 microcontroller. The idea is so good, and so simple, that it’s almost silly that it’s not standard hacker practice. And above all, it actually helps solve a problem that I’ve got. This is why I read Hackaday every day.
I2C is great in that it lets you connect up multiple devices to a pair of wires using a very bus architecture. Every device has its own address, the host calls them out, and hopefully all other devices keep quiet while just the right one responds. But what happens when you want to use a few of the same sensors, where each IC has the same address? The usual solution is to buy a multiplexer chip.
But many modern microcontrollers, like the ESP32, have an internal multiplexer setup that lets you map the pins with the dedicated hardware peripherals, usually at initialization time. Indeed, I’ve been doing it as an “init” task so long, I never thought to do it otherwise. But that’s exactly the idea behind [BastelBaus]’s hack – if you dynamically reassign the pins, you can do the I2C multiplexing with the chip you’ve got. This should probably work for any other chips that have multiple assignable pins for hardware peripherals as well.
Cool idea, but really simple. Why hadn’t I ever thought of it? I think it’s because I’ve always had this init / mainloop schema in my mind, which for instance the Arduino inherited and formalized in its setup()
and loop()
functions. Pin mappings go in the init section, right?
So what this hack really amounts to, for me, is a rethinking of what’s static and what’s dynamic. It’s always worth questioning your assumptions, especially when you’re facing a problem that requires a creative solution. Sometimes limitations are only in your mind. Have you had your mind opened recently by a tiny little hack?
On many of my projects, finding two acceptable pins free for the I2C bus is difficult enough. Looking for 3 or 4 gets more painful.
Exactly what I was going to say.
I create battery powered ESP32 devices that deep sleep after executing. I don’t even use the loop(). All my code is in setup() or in application functions. Use of the setup/loop construct isn’t actually necessary. In fact although both must be defined either one can be void. The setup/loop construct is just a convenience.
Or an inconvenience, considering that you still get the overhead of calling loop() every cycle and the Arduino core is doing some serial data handling in between, so you get non-deterministic timing for your code.
It’s a completely unnecessary complication, and I never understood why they had to do that – other than “don’t teach people how standard C/C++ works so they’d be afraid to use something else than Arduino IDE.”
i got some angle sensors that have a fixed address, which is kind of a problem when you are rebuilding and modernizing an old milspec joystick (from at least the ’60s, judging by some of the electrical components hanging off of it) and need 2 of them. you can read them through i2c but you can also output pwm on another pin with some setup. you can permanently burn the config but i dont want to do that in the proto stage. not wanting to use software i2c, i just took to hanging an attiny85 (i have many of the diminutive spiders) off each one, it bootstraps the sensor and then goes into a sleep mode. stupid simple.
I generally open up my mind with a long cycle ride, or if it’s raining, a pint of
Dead Rat Cider
(https://deadratciderfactory.onlineweb.shop/). Either way, it’s almost guaranteed that whatever the impasse was, it gets solved within 24 hours. Worst thing to do is keep trying to crack it when you know it’s a dead locked impasse.I have a dead rat, and a string to swing it!
One thing to note, that’s not entirely obvious is that you only really need to multiplex SCL, SDA can be shared.
Just double checking. Multiplexing SDA right? (Shared SCL)
No, he’s right. Especially if you’re running different speeds. Some 100kHz devices get very arsey when they see a 400kHz clock.
and it’s not hard to do i2c in software (both client and master), and you can have as many devices as you can find spare pins.. The protocol isn’t that hard, I’ve done it for multiple chips.
It isn’t hard, but it takes quote a bit of CPU time, if you want a reasonable datarate.
This trick works particularly well on ESP32, since you can map any digital function to almost any pin, as opposed to most other microcontrollers where the remapping options are fairly limited.
Personally, I don’t like I2C at all, as the CPU is still very much involved in the transfers. On SPI, you can setup large, fast transfers (10MHz and higher if you really want) to be processed using DMA, which leaves the CPU free for others tasks. It does take 3 pins plus one for each device, so it’s not ideal if you are really short on pins.
How long does pin reassignment take? I always assumed it went in the init section because it was a slow process.
You were doing so well, and then you had to mention the A-word…
What? Assumption? Assignable? I re-read the article twice to see if there was something I missed the first time around, lol
There are some nice projects that dynamically assign the pins, such as Ed Smallenburg’s ESP32 Radio (https://github.com/Edzelf/ESP32Radio-V2). The configuration is read from flash and the pins assigned accordingly. Everything is adjustable on the fly from a web page on the ESP32. Quite nifty really. If you don’t like which pins have been assigned to a rotary encoder, or the infrared receiver, or any other peripheral you just switch them in the web browser, save and restart. Built one of these myself recently on perfboard and wanted to reassign a couple of the pins to make the layout easy.