Close

Some Windows-related updates

A project log for Dr. PD USB-PD protocol analyzer

A fully featured, open-source USB Power Delivery protocol/power analyzer and programmable sink for testing, logging, and debug.

marco-tabiniMarco Tabini 04/24/2026 at 14:300 Comments

Every operating system has its quirks and flaws—you just get used to dealing with them when you have to face them every day. It’s only when you switch to another OS that things suddenly start to look weird.

Most of my daily work happens in macOS, with the occasional dip into Linux or BSD to maintain some server-side components. Dr. PD, however, needs to run well on Windows, where most of its users are going to be. Therefore, I make sure to test all major changes on my PC as well.

The most recent round of updates has added two important features that had long been on my to-do list: log persistence and over-the-air firmware updates directly from the web app.

A bit of background

Dr. PD’s firmware is written entirely in C++ and resides on the device itself. It uses TinyUSB to expose several USB interfaces for different purposes: USBTMC for device operation, CDC for debugging over serial, and a vendor reset interface used to place the device in DFU mode for flashing new firmware onto it. 

The web app then uses the WebUSB API to access the device’s USB interfaces and communicate with it. WebUSB is only supported in Chrome and Chromium-based browsers, but it is nonetheless a very convenient way of providing an essentially instant-on experience for the user: there are no drivers to install, no apps to set up, no accounts to create, and so forth. You just plug the device into your computer, and Chrome helpfully pops up a notification that directs you to the client app.

On Mac and Linux-based OSs, this is all seamless. No drivers need to be installed, and things “just work” (plus or minus the inevitable bugs). Reflashing the firmware requires a bit of extra work to avoid having to ask to reconnect to the device manually when you put it in DFU mode, but it's relatively easy to come up with a fully seamless experience without too much trouble.

On Windows… well, Windows is a bit more complicated.

First of all, Windows has very limited USB support without installing additional drivers. This means, for example, that WebUSB cannot talk directly to the USBTMC interface on Dr. PD unless you install dedicated software. This software is widely available, but I view the ability to just “plug-and-play” Dr. PD into your computer as a defining feature, and having to install a third-party driver completely undermines that.

Luckily, Windows does come with a default library, called WinUSB, that can be used to provide what Microsoft calls “simple” functionality over USB without dedicated drivers. It does have several limitations (such as the fact that only one client can be connected at any one time), but they do not affect Dr. PD.

Unfortunately, I can’t just move all the functionality under WinUSB, because the USBTMC interface would no longer be compatible with systems that depend on it, such as LabVIEW. That would make Dr. PD useless to anyone who wants to use it with an automated setup.

Thus, the solution I landed on was to implement both a traditional USBTMC interface and a WinUSB-based interface. The web app preferentially uses WinUSB so that it works properly under Windows, while Dr. PD remains compatible with every other piece of software that requires USBTMC.

Enter firmware updates

As you may know, the RP2350 MCU that runs Dr. PD's hardware comes with its own DFU implementation that allows flashing new firmware over USB. If you've ever uploaded firmware to a Raspberry Pi Pico, you've seen this interface at work.

Sadly, we can’t use this interface from the web app, because the RP2350 changes its product identifier when it enters DFU mode. This causes WebUSB to see it as a new, unpaired device that it has never encountered before and that, therefore, the user has not authorized Chrome to connect to. To make it possible to upload new firmware from inside the web app, we would have to require the user to pair with the device again, which would be confusing and likely error-prone.

The solution I landed on was to create my own custom bootloader, which gets launched by the built-in RP2350 bootloader. The custom code can then enter its own DFU mode without changing the device identifiers, allowing Chrome to recognize it as the same Dr. PD device it already has permission to connect to. The new firmware can then be flashed as a completely seamless operation.

Notably, when Dr. PD is in DFU mode, there is no reason for it to expose any of the USB interfaces that are normally available. Since the only allowed operation is uploading new firmware, exposing WinUSB, USBTMC, CDC, and so on would be completely redundant. Thus, when it enters DFU mode, Dr. PD effectively disconnects from the USB bus and then reconnects as a “simpler” device.

Alas, Windows likes to cache device descriptors, and it gets rather upset when it detects that a device for which it already has a record has reconnected with a completely different set of interface descriptors. When that happens, it decides that something catastrophic has occurred and disables the device altogether.

Eventually, I solved this problem by simply exposing all the interfaces, even in DFU mode. The web frontend can tell whether the device is in DFU mode and act accordingly. Simple enough, but certainly more of a rabbit hole than I originally expected.

In case you want to implement in your own project, this new set up is now also part of the T76 Instrument Core.

Log permanence pains

From the beginning, it was important for me to give Dr. PD strong logging capabilities. While most USB-PD transactions are short and involve just a handful of messages, it is not uncommon to deal with complicated exchanges, and sometimes it takes hours or days for a problem to materialize.

Thus, I wanted to make sure that Dr. PD could handle an ideally limitless amount of log data — at least to the extent supported by storage on the host computer. Given that the frontend runs in a web browser and works completely client-side, with no backend for storing data, implementing this functionality was a bit more involved than you might imagine.

Dr. PD actually ships with a copy of SQLite implemented in Web Assembly. This brings the power of SQLite to the browser with near-native performance, giving the client access to a powerful database engine that has access to local storage through OPFS. As a result, you can save several days' worth of both analog and digital traces without any noticeable loss of performance.

At least, that's true on macOS and Linux. On Windows… well, Windows is a bit more complicated.

The initial implementation of the logging feature took a fairly naïve approach to saving data as it streamed in from the device. Favouring durability over performance, it committed each row to the database individually. Admittedly, this required quite a bit of back-and-forth between the SQLite code and the OPFS driver, but since it wasn’t noticeably affecting performance under macOS, I didn’t think much of it. After all, an easy solution that works well means less complexity in the code.

It turns out that, on Windows, my naïveté was not well received: OPFS was so slow that the frontend couldn’t keep up with the device and ended up dropping messages under even moderate streaming loads.

In this case, the solution was to batch the writes and commit data to the database only every 100 ms or so, in a separate thread. This makes Windows happy and keeps things zippy for everyone.

All these changes are now in the 0.9.10 release.

Discussions