I’m no stranger to quirky USB power systems; indeed I have ranted about USB chargers for over ten years already, well before most people would have heard brands such as Anker, or technology such as GaN (well, maybe on NCIS), though to be honest I didn’t hear any of that either at the time. Like many of my usual half-thought-out projects, I could see that this (eventually) would become an important topic, but I had no idea how that would be, and I couldn’t for the life of me make good use of my gut feeling. This particular caveat of my insights is still cursing me to this day.
In the past thirteen years, though, a lot of things changed: gone are the days of USB Mini B, and even of Micro B, since most devices are now using USB Type-C plugs. And instead of “just” needing to care about the maximum current supplied by a charger, we’re caring about protocols and voltages! USB Power Delivery (USB-PD) has thankfully delivered (sorry) on the promise of compatibility, to some point, but that is neither a given nor an easy task.
Indeed, I have found myself complaining a few times how, despite USB Type-C and PD bringing us the ability to have a real universal plug, a lot of modern hardware still uses barrel jacks. Well, I now know why, and I don’t think I’ll be as annoyed by them anymore, as it turns out it’s a lot more annoying than I would have expected it to be.
Let’s start from… the start. When I decided to go back at building my own acrylic lamp, I decided to avoid the difficulty of soldering microUSB plugs myself, and instead I drawn my own symbol to solder a USB Type-C plug on it instead. Type-C meant that it would use the same cable as most of my other devices, instead of being stuck with finding an older cable to power them with — and while right now I still have a good selection of microUSB cables, experience tells me that the supply will start going down fast.
When designing those, I found a very useful tutorial on the PCBway website (not a sponsor, not an affiliate link) that describes pretty well what needs to be done to include a USB Type-C plug for simpler projects, using just power, or at most USB 2.0 speeds. Both of these cases can rely on simplified Type-C plugs, since not all of the 20 pins need to be exposed for them to work. For the lamps, since they use WS2812B LEDs that are powered at 5V, I could use the simpler 6p Type-C plug (HDGC Type C-DB-109PWB) for just power, or the 16p USB 2.0 plug (DGBZ BTC-TC16S-23) for the version that (attempted) to integrate the USB-to-Serial adapter — in either case, I configured the resistors to allow up to 5A of draw.
Drawing the symbol for the power-only plug was a lot easier — particularly since it’s just six positions plus the shield itself — the 16p one instead was a lot more complicated, particularly since it includes four grounds, and indeed the first time I sent it to production, I had forgotten to connect those grounds to the actual grounds of the board, oops! Not that it would have helped to, since when I drawn the symbol I appear to have reversed the order of the pins, so that the USB data pins (positive and negative — it’s a differential pair) wound up inverted.
But that meant I could finally look at one of my next projects in the backlog: a charging cradle (or add-on) for my Brother PTE300VP label maker, which I have already discussed in the context of my Saleae adapter harnesses. This particular label maker is one of my favourite tools. While I do still have the LetraTag I use for random labeling here and there, the fact that the Brother one has both a rechargeable battery and can power straight out of the charger makes it still easy to use… most of the time. The only problem is that, since it has a rechargeable battery, the barrel jack is a center negative barrel jack — it was explained to me a long time ago why the center positive barrel jacks are risky when used with rechargeable batteries, but I honestly don’t remember why or where was it. This is literally the only device in my office with such a configuration, and a risky one at that, since most devices don’t have polarity protection for barrel jacks, and using the wrong polarity leads to devices just… burning up.
What I originally intended to build was a “charging cradle” I could leave on one of the shelves, and not have to worry about which charger it is, though my friend Vanya suggested that maybe I could just swap out the bottom bumper with something holding an USB-PD circuit that provides the necessary 12V, which sounded great…
… until I started looking into how to achieve 12V with USB-PD.
So the first problem is that if you want anything that is more than 5V you need to implement the full Power Delivery protocol, which is non trivial. I don’t quite understand the protocol and its signaling, but the TL;DR is that it’s a low-voltage (1.8V if I’m not mistaken) protocol with strict timing requirements. While this is not impossible to implement on an MCU directly, at least with an appropriate RTOS, the level shifting alone is complicated for most hobbyists MCUs that usually run at 1.8V. In addition to this, there’s different versions of the protocol which needs to be negotiated, with different set of features. PD 1.0, again to the best of my understanding, does not appear to include 12V as a standard voltage, but later revisions of the protocol allow you to select a “base” voltage (such as 9V or 15V) and then adjust it in 50mV increments — which allows you to actually select the 12V.
In addition to this, there’s no guarantee that the source you’re talking with is even capable of providing you the voltage you’re asking — which is why for PD negotiation many standalone controllers let you usually provide up to three “PDOs”: the first being mandated to be 5V, while the other two can be configured. Only after negotiation is complete you would know what voltage output you would get, and you almost always won’t want to power up anything over 5V until the negotiation is done.
With all of this considerd, almost all boards out there that use USB-PD do that through the use of a dedicated controller or at least PHI chip — basically every manufacturer you can think of makes one, and most of them have I²C or SPI inputs to configure them. Some of them allows programming of PDOs in non-volatile memory, while others will not negotiate anything until you request them to. You can find plenty of examples of these controllers being used on even Open Hardware projects, but turns out they are not always the most approachable.
Most of the options I found appear to have written very case-specific code for whichever controller they are using, pairing the two parts very closely, while others appear to go the full generic path with layers upon layers of abstractions. The SparkFun Power Delivery Board, which uses a programmable STmicro controller, should allow configuring a PDO for 12V and be done with it, so it would be suitable (directly, or by importing and adapting the schematics) for the cradle I was thinking of.
But then, I realized that USB-PD would have a few other users in my large collection of half-baked, half-complete projects, including my hope to eventually get my own, customizable Christmas lights, a sixteen years old idea — which is nowadays pretty much obsolete, as I’m sure there’s some Zigbee Christmas lights if I go and look for them.
Most of the LED lights that you can buy for cheap (from either Amazon or AliExpress) seem to be using a 21V power supply, inverting the polarity on the two-wires output to give the effect of independent light groups (even though there’s only two groups.) With a bit of work I’m sure ESPHome would be able to control those, since it already has most of the platform support to PWM LEDs for various effects — but as it turns out, there’s no easy way to use USB-PD with ESPHome that I could find!
And it’s not for the lack of semantics: ESPHome supports power supply components although right now it appears to only document on how to use them with ATX PSUs. In my mind, if I was to finally get a ESPHome-based controller for string lights (they don’t strictly need to be Christmas spirited), would be to only enable the high-voltage PD-negotiated supply when the lights are meant to be turned on, keeping the device quiescent at 5V.
The abstractions available to ESPHome seemed actually very appropriate for providing a two-layers indirection: a generic USB-PD interface allowing you to configure PDOs, and controller-specific drivers that allow hardware with different controllers. Turns out that there is already such an interface by the way, which appears to have been published by Intel, and is called USB Type-C™ Connector System Software Interface [UCSI], which is also supported by Microsoft in Windows itself.
At that point I started going through tons of datasheet to find something I could work with on the desk to write the code, and then figure out how to integrate in a board — most of the chips should be just I²C or SPI targets, so it wouldn’t be much of an issue, except that the ready-made devkits to develop for them are expensive. As in, ridiculously so, some of them up to £300! That explains why there’s so little out of the box support for these chips, and why so many devices still use barrel jacks even if they are well below the 240W limit of USB-PD.
It doesn’t help that the more well-understood controllers appear to have fallen out of favour, in part due to the chip shortages, and in part due to the evolution of the PD protocol. Or they are just not reachable in sizes that make sense for hobbists — for instance the IP2721 by Injoinic, which I found documented and used in a few medium-to-large batches of hobbists projects, only appears to be available on LCSC for me, which excludes the ability to integrate it in personal projects!
Anyway, since I’m a well paid software engineer, and I was looking for a new side project to distract myself, I started looking further to what I could be doing about this hole in our open ecosystem — and I eventually found Microchip’s UPD301C, which is particularly interesting as it is a single chip combining their UPD350 controller and a SAMD20. This was particularly interesting because the SAMD20 is the same class of MCU that I’ve been using on my Birch Books project — the Trinket M0 uses an ATSAMD21E18, while the UPD301C uses an ATSAMD20E16, which mostly means the latter is lacking the full-speed USB support, DMA and timer counter.
Add to that the fact that their simple sink devkit EV11L78A is one of the cheapest out there (though it does not come with an in-system programming interface, so you have to supply your own — I had already a SEGGER J-Link Edu Mini I used to salvage an Adafruit board before) and that (as far as I can tell) it can connect to I²C via the provided header, and I was sold for an idea of re-working my Birch Books solution to a new board using all-SMD components. Particularly given how easy it is to find SMD LEDs on a wire for model making that are expected to take 12V (but I would probably under-voltage at 9V to use.)
The firmware that the board comes from (Microchip USB Power Delivery Software Framework — PSF) is not the friendliest, both in terms of license and code organization, so I didn’t really want to base myself off that. Instead I decided to finally bite the bullet and learn a bit about the Zephyr RTOS, which is a Linux Foundation project sponsored, among others, by my current employer, and for which I already had a contact or two. If you’re not well connected with the embedded scene this probably has passed by you, but it appears Google is now using this project for the Embedded Controller in their Chromebooks, which would make hacking their keyboard a lot easier (maybe.)
This turned out to be a bit more complicated than I foresaw. First of all, the UPD301C embeds the UPD350B, which does not support standalone operation, which means it’s not just a matter of configuring the right registers and moving on, but you actually need to implement the right steps to configure the CC lines. Thankfully, Zephyr does support these type of controllers (called Type-C Port Controllers, tcpc), though this particular case turns out to be complicated.
As the architecture itself appeared to be already supported by Zephyr (it even supports some of the Adafruit M0 boards such as the Feather and the aforementioned Trinket M0), getting something working took me just a few hours. Unfortunately, getting it to work took me a little more work, and a little more expenses, as I decided to compare it with an officially supported board, the SAMD20 XplainedPro.
But this is now a different yak to shave that I will write about in another post. You should just know that the basics of the board are now working correctly with Zephyr, my code was merged, and I both refreshed my memory about remote debugging and learnt new things about ARM in particular.