Overview
USB C Power Delivery is terrific -- your compatible devices can request the proper voltage from a PD power supply. But you know what's even cooler? Telling your supply exactly which voltage you want so you can drive all sorts of DC powered gizmos without searching around for wall warts!
This project shows you how to build an interface to communicate with your PD supply as well as display the current usage of your devices. It uses an ESP32-S2 Reverse TFT Feather running CircuitPython plus two I2C breakouts -- the INA291 and HUSB238 for current measurement and voltage selection (such as 5V, 9V, 12V, 15V, & 20V).
This is a plug-and-play project -- no soldering required! Build it on a swirly grid and encase it in an optional 3D printed enclosure.
Multi-port USB Power Delivery Supply
Such as this one from Anker.
Page last edited September 04, 2024
Text editor powered by tinymce.
Build the Power Deliverer
Swirly Grid
Connect one STEMMA QT cable to the Feather before fastening it to the swirly grid, since the port will be hard to access afterwards.
Fasten the Feather to the grid as shown using M2.5 x 12mm F-F hex standoffs and short screws.
Mount the breakout boards with M2.5 x 6mm F-F standoffs as shown.
Connections
The Power Deliverer is assembled without any soldering. Plugging in STEMMA QT cables and screwing in wires to terminal blocks is all that's required.
Follow the Fritzing diagram above to see how it all goes together.
First, plug a STEMMA QT cable from the Feather to the INA219 board.
A second STEMMA QT cable connects the INA219 to the HUSB238 Power Delivery Dummy.
To measure current, the INA219 is connected between the HUSB238 and the DC powered device you're running:
- HUSB238 V+ to INA219 V-
- INA219 V+ to DC output V+
- HUSB238 GND to DC output GND
The output DC power socket is the female half of the waterproof DC power cable set which can either run out of the case freely or be zip-tied to the swirly grid as shown.
The case was designed so the 10x2 swirly grid snap fits into the base. There are cutouts for the two USB C ports, the DC power line, and the TFT display.
Four push tabs can be used to press the Feather's buttons.
Download the STL model linked below to print on an FDM printer. You can use the STEP file as a launching off point for modifications and remixes.
Snap Into Place
Feed the Power Deliverer into the case as shown, making sure to line up the ports with the holes.
Page last edited September 04, 2024
Text editor powered by tinymce.
CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
CircuitPython Quickstart
Follow this step-by-step to quickly get CircuitPython running on your board.
Click the link above to download the latest CircuitPython UF2 file.
Save it wherever is convenient for you.
Plug your board into your computer, using a known-good data-sync cable, directly, or via an adapter if needed.
Double-click the reset button (highlighted in red above), and you will see the RGB status LED(s) turn green (highlighted in green above). If you see red, try another port, or if you're using an adapter or hub, try without the hub, or different adapter or hub.
For this board, tap reset and wait for the LED to turn purple, and as soon as it turns purple, tap reset again. The second tap needs to happen while the LED is still purple.
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called FTHRS2BOOT.
Drag the adafruit_circuitpython_etc.uf2 file to FTHRS2BOOT.
Page last edited September 04, 2024
Text editor powered by tinymce.
Code the Power Deliverer
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: Copyright (c) 2024 John Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
USB C PD power supply w HUSB238
pick voltages and then set them, measures high side current with INA219
"""
import time
import board
import keypad
import displayio
import terminalio
from adafruit_display_text import label
from adafruit_display_shapes.rect import Rect
import adafruit_husb238
from adafruit_ina219 import INA219
i2c = board.I2C()
tft_d0_button = keypad.Keys((board.D0,), value_when_pressed=False, pull=True)
tft_buttons = keypad.Keys((board.D1, board.D2), value_when_pressed=True, pull=True)
# Initialize INA219 current sensor
ina219 = INA219(i2c)
TXTCOL_VOLT = 0x8f00cd
TXTCOL_CURR = 0xb30090
TXTCOL_DIM = 0xCD8F00
TXTCOL_HEAD = 0xCD8F00
TXTCOL_BTN = 0xa0a0a0
BGCOL = 0x220030
display = board.DISPLAY
group = displayio.Group()
background_rect = Rect(0, 0, display.width, display.height, fill=BGCOL)
group.append(background_rect)
warning_text = "plug in USB C PD cable, press reset"
FONT = terminalio.FONT
display.root_group = group
RUNNING = None
PLUGGED = None
# Initialize HUSB238 PD dummy
try:
pd = adafruit_husb238.Adafruit_HUSB238(i2c)
RUNNING = True
PLUGGED = True
except ValueError:
print("plug in a USB C PD cable, then press reset")
RUNNING = False
PLUGGED = False
warning_label = label.Label(
FONT, text=warning_text, color=0xdd0000,
scale=1, anchor_point=(0,0),
anchored_position=(20, 10)
)
group.append(warning_label)
#stop the code here
while not RUNNING:
pass
while RUNNING:
voltages = pd.available_voltages
print("The following voltages are available:")
for i, volts in enumerate(voltages):
print(f"{volts}V")
v = 0
if pd.attached:
pd.voltage = voltages[0]
print(f"Voltage is set to {pd.voltage}V/{pd.current}A")
display = board.DISPLAY
group = displayio.Group()
background_rect = Rect(0, 0, display.width, display.height, fill=BGCOL)
group.append(background_rect)
vert_bar = Rect(40, 0, 3, display.height, fill=0x000000)
group.append(vert_bar)
FONT = terminalio.FONT
header_label = label.Label(
FONT, text="Power Deliverer", color=TXTCOL_HEAD,
scale=2, x=50, y=8
)
group.append(header_label)
voltage_label = label.Label(
FONT, text=str(voltages[0])+"V", color=TXTCOL_VOLT,
scale=5, anchor_point=(0,0),
anchored_position=(50, 20)
)
group.append(voltage_label)
current_label = label.Label(
FONT, text="0mA", color=TXTCOL_CURR,
scale=5, anchor_point=(0,0),
anchored_position=(50, 80)
)
group.append(current_label)
go_label = label.Label(FONT, text="set", color=TXTCOL_BTN, scale=2, x=1, y=6)
group.append(go_label)
up_label = label.Label(FONT, text="+v", color=TXTCOL_BTN, scale=2, x=1, y=display.height//2-2)
group.append(up_label)
down_label = label.Label(FONT, text="-v", color=TXTCOL_BTN, scale=2, x=1, y=display.height-12)
group.append(down_label)
display.root_group = group
while True:
tft_d0_button_event = tft_d0_button.events.get()
if tft_d0_button_event and tft_d0_button_event.pressed:
try:
print(f"Setting to {voltages[v]}V!")
pd.voltage = voltages[v]
voltage_label.text=str(voltages[v]) + "V"
voltage_label.color=TXTCOL_VOLT
print(f"It is set to {pd.voltage}V/{pd.current}A")
print()
PLUGGED=True
except OSError:
print(warning_text)
voltage_label.text="replug"
current_label.text="USB C"
PLUGGED=False
if PLUGGED:
tft_buttons_event = tft_buttons.events.get()
if tft_buttons_event and tft_buttons_event.pressed:
if tft_buttons_event.key_number == 0:
v = (v + 1) % len(voltages) # maybe have this stop at max
voltage_label.color=TXTCOL_DIM
voltage_label.text="["+str(voltages[v]) + "V]"
print(f"Voltage will be set to {voltages[v]}V")
if tft_buttons_event.key_number == 1:
v = (v - 1) % len(voltages) # maybe have this stop at min
voltage_label.color=TXTCOL_DIM
voltage_label.text="["+str(voltages[v]) + "V]"
print(f"Voltage will be set to {voltages[v]}V")
current = ina219.current # current in mA
# power = ina219.power # power in watts
current_label.text= str(abs(int(current))) + "mA"
if ina219.overflow:
print("Internal Math Overflow Detected!")
print("")
time.sleep(0.2)
How it Works
Library Imports
First, we import all the necessary libraries -- these allow us to interact with the current sensor and the Power Delivery dummy, as well as display text and shapes on the TFT screen.
import time import board import keypad import displayio import terminalio from adafruit_display_text import label from adafruit_display_shapes.rect import Rect import adafruit_husb238 from adafruit_ina219 import INA219
i2c = board.I2C() tft_d0_button = keypad.Keys((board.D0,), value_when_pressed=False, pull=True) tft_buttons = keypad.Keys((board.D1, board.D2), value_when_pressed=True, pull=True)
ina219 = INA219(i2c)
TXTCOL_VOLT = 0x8f00cd TXTCOL_CURR = 0xb30090 TXTCOL_DIM = 0xCD8F00 TXTCOL_HEAD = 0xCD8F00 TXTCOL_BTN = 0xa0a0a0 BGCOL = 0x220030 display = board.DISPLAY group = displayio.Group() background_rect = Rect(0, 0, display.width, display.height, fill=BGCOL) group.append(background_rect)
Initialize HUSB238
Here, we set up the USB-C PD controller dummy so we can ask the PD supply for different voltages. If the USB-C PD supply is unplugged, it displays a warning message rather than crash the code.
try:
pd = adafruit_husb238.Adafruit_HUSB238(i2c)
RUNNING = True
PLUGGED = True
except ValueError:
print("plug in a USB C PD cable, then press reset")
RUNNING = False
PLUGGED = False
warning_label = label.Label(
FONT, text=warning_text, color=0xdd0000,
scale=1, anchor_point=(0,0),
anchored_position=(20, 10)
)
group.append(warning_label)
#stop the code here
Main Loop
In the main loop, the available voltages are displayed and button presses handled -- these change the voltage settings and update the display with current readings.
while True:
tft_d0_button_event = tft_d0_button.events.get()
if tft_d0_button_event and tft_d0_button_event.pressed:
try:
print(f"Setting to {voltages[v]}V!")
pd.voltage = voltages[v]
voltage_label.text=str(voltages[v]) + "V"
voltage_label.color=TXTCOL_VOLT
print(f"It is set to {pd.voltage}V/{pd.current}A")
PLUGGED=True
except OSError:
print(warning_text)
voltage_label.text="replug"
current_label.text="USB C"
PLUGGED=False
if PLUGGED:
tft_buttons_event = tft_buttons.events.get()
if tft_buttons_event and tft_buttons_event.pressed:
if tft_buttons_event.key_number == 0:
v = (v + 1) % len(voltages)
voltage_label.color=TXTCOL_DIM
voltage_label.text="["+str(voltages[v]) + "V]"
print(f"Voltage will be set to {voltages[v]}V")
if tft_buttons_event.key_number == 1:
v = (v - 1) % len(voltages)
voltage_label.color=TXTCOL_DIM
voltage_label.text="["+str(voltages[v]) + "V]"
print(f"Voltage will be set to {voltages[v]}V")
current = ina219.current
current_label.text= str(abs(int(current))) + "mA"
if ina219.overflow:
print("Internal Math Overflow Detected!")
time.sleep(0.2)
Page last edited September 04, 2024
Text editor powered by tinymce.
Use the Power Deliverer
To use the Power Deliverer you'll plug in your PD supply USB C cable to the HUSB238 (the white cable in these photos).
Plug in the second USB C cable to the Feather.
Plug in a DC barrel plug for the powered device -- the fan in these photos.
Select a higher or lower voltage with the two lower buttons (+/-) on the left side of the device. To set that voltage, press the top button.
The current draw is displayed in the lower section of the screen.
Should the USB PD supply become unplugged the screen will let you know to re-insert the plug.
Page last edited September 04, 2024
Text editor powered by tinymce.