last updated: 2025-05-20
Mid 2021 I backed the promising Luna hardware from Great Scott Gadgets on crowd supply. In february 2023 Luna hardware was renamed to Cynthion and my Cynthion was shipped mid 2024. I will use Cynthion here as USB 2.0 protocol analyzer (Low, Full, and High Speed).
The DP100 is a tiny power supply with 2 USB connectors. The USB-C connector is the power input up to 32 V, 5 A with an USB-PD power supply (or another DC alimentation up to 32 V). It has a tiny OLED display, three buttons and an adjustment wheel.
The second USB-A connector can also be used to provide power (5 V/1 A) but is primarily needed to connect the device to a PC and to use the PC Windows software.
I don't use Windows, and I wanted a software for Linux especially as the usage of the device itself is not very intuitive and the buttons and the display are tiny (if you are only interested in the software look here: https://www.weigu.lu/other_projects/python_coding/dp100_manipulator/index.html).
To create a software, we need to understand the proprietary protocol used on USB.
So let's start :)
First we clone the repository to our favourite folder:
git clone https://github.com/greatscottgadgets/cynthion.git
There is a good documentation on readthedocs: https://cynthion.readthedocs.io/en/latest/. So let's concentrate on the essential.
Connect the Cynthion device with the host control computer (running the software, man in the middle). Use the USB-C socket labelled CONTROL
.
To avoid doing everything as root, we copy the udev rules to /etc/udev/rules.d
, reload the rules and apply them to the devices already plugged in:
cd cynthion
sudo cp cynthion/python/assets/54-cynthion.rules /etc/udev/rules.d
sudo udevadm control --reload
sudo udevadm trigger
The Cynthion software is written in Python. So we use a virtual environment to deploy it. More on virtual environments: https://www.weigu.lu/other_projects/python_coding/using_venv/index.html.
python3 -m venv cynthion-venv
source cynthion-venv/bin/activate
pip3 install --upgrade cynthion
Now let's test if everything works as expected:
cynthion info --force-offline
Perhaps we need to update to the latest firmware:
cynthion update
cynthion info
As Cynthion can do different things, we need to configure it as analyzer by loading the right bitstream to the FPGA:
cynthion run analyzer
To use the USB Analyzer as the default bitstream for the FPGA we can flash it to the FPGA:
cynthion flash analyzer
cynthion info
Now we can leave the virtual environment with deactivate
. To reuse the cynthion hardware software we do again a source cynthion-venv/bin/activate
.
The analyzing software is called Packetry and is written in Rust and uses GTK4. Packetry is fast and analysis the hardware USB bus packets. It can display them in a hierarchical way, that facilitates the interpretation of the traffic. You can save also the data as pcap
-file and look at it with wireshark. But wireshark is less suitable to analyse USB data You can find explanations in the ShmooCon 2025 video.
The simplest way is to use an AppImage: https://github.com/greatscottgadgets/packetry/releases.
But its also interesting to learn how to run a Rust program on Linux. First we clone the repo:
git clone https://github.com/greatscottgadgets/packetry.git
cd packetry
Then we install rustc with the rustup tool and build Packetry with cargo:
sudo apt install rustup build-essential libgtk-4-dev
rustup default stable
rustc -V
cargo build --release
The Packetry binary is now in the folder target/release/
.
I had two problems with Packetry.
The first had to do with GTK4 in KDE (Plasma Wayland). I did not see the arrows to dive deeper. For this I had to change the GTK theme (Appearance & Style -> Colours & Themes -> Application Style -> Configure GNOME/GTK App Style) to any other Theme than Breeze.
The second was that I got a lot of invalid groups and faulty packages. This was because I used the same USB host controller on my PC for CONTROL
and TARGET C
. Choosing another USB socket with another controller solved this.
After solving these problems, I was able to sniff the traffic.
We connect the PC with the software for our USB device (for me a Windows machine in Virtualbox) to the USB-C port labelled TARGET C
. Now we start the record process in Packetry (red dot). After that we connect the USB device to the USB A port labelled TARGET A
. After some seconds we can stop and save the enumeration process in a pcap
-file.
If we get nothing, the speed is wrong. Set the speed to low, full or high according to your device. If you don't know the speed, try one after the other. The DP-100 used here works with full speed (12 Mbps).
Ok, we could use lsusb to get the device data, but in the device window it is easier to check than in a terminal. What do we have here?
The Manufacturer string is ALIENTEK, Product string ATK-MDP100, Vendor ID:0x2E3C, Product ID: 0xAF01
This helps already to write a udev rule for Linux, so that we don't need to be root to access the device.
cd /etc/udev/rules.d
sudo nano 55-ATK-MDP100.rules
Copy the following lines in the editor and save (Ctrl+o, Ctrl+x) and reload the rules.
# DP100
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e3c", ATTRS{idProduct}=="af01", MODE="0660", GROUP="plugdev"
sudo udevadm control --reload
sudo udevadm trigger
We can test test the udev rules with:
udevadm test /sys/class/hidraw/hidraw0
ls -l /dev/hidraw0
We get one configuration with one HID interface. The HID Interface has a descriptor with 32 bytes. Let's ask KI (chat mistral) to get the meanings and create a table. Here the result:
Byte Index | Value (Hex) | Description |
---|---|---|
0-1 | 06 FF | Usage Page (Global Item) - Vendor-defined usage page (0xFF). |
2 | 00 | Reserved - Reserved byte, typically set to 0x00. |
3-4 | 09 01 | Usage (Local Item) - Vendor-defined usage (0x01). |
5-6 | A1 01 | Collection (Main Item) - Begins an application collection. |
7-8 | 15 00 | Logical Minimum (Global Item) - Minimum value is 0x00. |
9-10 | 25 FF | Logical Maximum (Global Item) - Maximum value is 0xFF. |
11-12 | 75 08 | Report Size (Global Item) - Size is 8 bits. |
13-14 | 95 40 | Report Count (Global Item) - Count is 64 fields. |
15-16 | 09 01 | Usage (Local Item) - Vendor-defined usage (0x01). |
17-18 | 81 02 | Input (Main Item) - Defines an input report (Data, Variable, Absolute). |
19-20 | 95 40 | Report Count (Global Item) - Count is 64 fields. |
21-22 | 09 01 | Usage (Local Item) - Vendor-defined usage (0x01). |
23-24 | 91 02 | Output (Main Item) - Defines an output report (Data, Variable, Absolute). |
25-26 | 95 01 | Report Count (Global Item) - Count is 1 field. |
27-28 | 09 01 | Usage (Local Item) - Vendor-defined usage (0x01). |
29-30 | B1 02 | Feature (Main Item) - Defines a feature report (Data, Variable, Absolute). |
31 | C0 | End Collection (Main Item) - Ends the collection. |
We have a vendor designed usage with an application collection. No HID class is used. HID is used to avoid a driver. The exchange is done with a field of 64 byte (8 bit). The meanings are only known to the vendor.
Next we use the windows software and connect to the device, switch on and off, and disconnect again. By analyzing the IN and OUT transactions (we have only 2 endpoints), we see that the host PC sends always 0xFB as first byte (OUT) and the device 0xFA (IN). If we look at short commands like:
FB, 30, 00, 00, 31, 0F,
FA, 35, 00, 01, 01, 33, 88,
we can guess that the second byte from the host is perhaps a command. In the first OUT transactions we see 0x30, 0x10, 0x35 and 0x40. The byte is repeated by the device if it answers. The third byte is always 0x00. The fourth byte (host) is 0x00 followed by 2 byte or 0x01 followed by 3 byte, so perhaps the length and a checksum. I asked again the KI and it took a bit longer to find that we have indeed the length of the following data followed by a checksum called CRC-16-Modbus. What it made it harder to find this was the fact, that the lowest byte comes first also called as little endian.
Byte | 0 | 1 | 2 | 3 | 4–3+n | 4+n | 5+n |
---|---|---|---|---|---|---|---|
Meaning | 0xfb/0xfa | command | 0x00 | len data n | ... | crcL | crcH |
Now it helps to use the Transaction tab of Packetry to see the answer to a command. We see that the first answer of the device is:
FA, 30, 00, 10,
C8, 4E, 00, 00, 00, 00, 5E, 4C, 2F, 01, 2D, 01, CE, 13, 02, 00,
92, 29,
0E, 00, 0B, 00, AA, 00, 15, 89, 64, 12, 00, 00, 27, 70, 1D, 27,
D9, 05, E8, 07, 08, 1D, 81, 49, 00, 00, 00, 00, 00, 00, 00, 00,
00, 00, 00, 00, 00, 00, 00, 00, 00, 00
Ok strange. The length is 0x10, so 16 byte. I use an online calculator for the crc (crccalc.com) and yes, the result is 0x2992
for the first 19 byte.
This fits with LB first (little endian). I assume we can ignore the following byte?
Now let's also assume that the data uses 2 byte (unsigned integer) and we use little endian. In decimal we get:
20168, 0, 0, 19550, 303, 301, 5070, 2
Hey cool. By comparing with the win software we find the input voltage (20.16 V), perhaps the output voltage and current (0) and the system temperature (30.3 °C).
Ok let's connect a resistor and activate the Output.
FA, 30, 00, 10,
BD, 4E, 8D, 13, 17, 00, 5E, 4C, 43, 01, 48, 01, CD, 13, 01, 00,
F9, 48,
The CRC is ok. Now we get
19901, 5005, 23, 19550, 323, 328, 5069, 1
This shows now we definitively have the output voltage of 5 V and a current of 23 mA.
So the 0x30 command returns basic information. It seems we get two temperatures and also 2 additional voltages (19.55 V, 5,069 V (USB voltage?))
From host (PC):
Byte | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Basic Set | 0xfb | 0x30 | 0x00 | 0x00 | crc 0x31 | crc 0x0f |
From device (DP100):
Byte | 0 | 1 | 2 | 3 | 4–5 | 6–7 | 8–9 | 10–19 | 20–21 |
---|---|---|---|---|---|---|---|---|---|
Basic Info | 0xfa | 0x30 | 0x00 | 0x10 | vin | vout | iout | ... | crc |
Next let's look at the answer of the 0x10 command:
FA, 10, 00, 28,
41, 54, 4B, 2D, 44, 50, 31, 30, 30, 00, FF, FF, FF, FF, FF, FF,
0E, 00,
0E, 00,
0B, 00,
AA, 00,
15, 89, 64, 12, 00, 00, 27, 70, 1D, 27, D9, 05,
E8, 07, 08, 1D,
81, 49
There is some ASCII code! The first 9 byte give "ATP-DP100" :). So we have here device infos.
Next we see two times 0x0E
= 14. My hardware version is 1.4 and also the software version is 1.4.
My serial number is 1D27D905.
From host (PC):
Byte | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Basic Set | 0xfb | 0x10 | 0x00 | 0x00 | crc 0x30 | crc 0xc5 |
From device (DP100):
Byte | 0 | 1 | 2 | 3 | 4–19 | 20–21 | 22–3 | 24–35 | 36–39 | 40–43 | 44–45 |
---|---|---|---|---|---|---|---|---|---|---|---|
Device Info | 0xfa | 0x10 | 0x00 | 0x28 | name | hver | sver | ... | serial nr | ... | crc |
From here on I cheated and looked in an older article in the french Hackable mag, where Denis Bodor already worked on the DP100 USB communication.
And it was good that I cheated, because the command 0x35
(Basic Set) is very complicated and not intuitive at all.
Even with all the available information I had to use Cynthion to really understand what is happening.
Denis found the following file from lessu, that helped a lot: https://github.com/lessu/open_dp100/blob/master/DP100_Protocol.md.
So let's look at this weird command.
The DP100 has 10 profiles numbered from 0–9 with voltage voset and current ioset. One of this profiles is active and is used if we switch on. So we have no command to change the voltage or current without using the profiles!
If we send the command 0x35
with the data byte 0x80
we get the data for the active profile. This is the only command to know which profile is active!
From host (PC):
Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Basic Set | 0xfb | 0x35 | 0x00 | 0x01 | 0x80 | crc 0xce | crc 0x28 |
From device (DP100):
Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6–7 | 8–9 | 10–11 | 12–13 | 14–15 |
---|---|---|---|---|---|---|---|---|---|---|---|
Basic Set | 0xfa | 0x35 | 0x00 | 0xa | index | state | vo_set | io_set | OVP | OCP | crc |
Index gives us this information. The second data byte state informs us if the output is on or off!
If we replace 0x80
with 0x00
–0x09
we can read voset and ioset for all profiles.
To change a profile, we need to read the profile and save the data. Then we change the first data byte (byte 4) and the values for voltage and current.
We recalculate the CRC and send the data back.
The upper nibble of byte 4 must be 0x4 and the lower nibble is the profile number (e.g. to change third profile (number 2) we send 0x42
).
If we want to change voltage or/and current we need to activate the profile with the wanted values or even first set the values to a profile and than activate it.
To do so we need to read the profile and save the data. Then we change the first data byte (byte 4), recalculate the CRC and send the data back.
The upper nibble of byte 4 must be 0xA and the lower nibble is the profile number (e.g. to activate the forth profile (number 3) we send 0x83
).
To switch on/off we have to look which profile is activated. Then we read this profile, save the data and change the first and the second data byte (Byte 4 and 5), recalculate the CRC and send the data back.
The upper nibble of byte 4 must be 0x2 and the lower nibble is the profile number (e.g. to switch on/off the active profile number 5 we send 0x25
).
Byte 5 is 0x01
to switch on and 0x00
to switch off.
My python software for the DP100 can be found here: https://github.com/weigu1/dp100_manipulator, and information about the software here: https://www.weigu.lu/other_projects/python_coding/dp100_manipulator/index.html