One Button Based 5-20V USB-C PD Voltage Selector

by sainisagar7294 in Circuits > USB

129 Views, 2 Favorites, 0 Comments

One Button Based 5-20V USB-C PD Voltage Selector

mini_final1.png

USB-C PD Selects the Voltage from 5V to 20V at the Press of a Button with CH224K + CH32V003 RISC-V

Supplies

Components Required

Here is the complete list of components I used for this project:

  1. USB Type‑C Connector
  2. CH224K
  3. CH32V003F4P6
  4. LM317DCYR
  5. LEDs
  6. 1 kΩ Resistors
  7. 10 kΩ Resistors
  8. Voltage Divider Resistors
  9. 100 nF Capacitors
  10. 1 µF Capacitors
  11. 16 pF Capacitor
  12. Push Button Switch
  13. Screw Terminal Block
  14. Custom PCB from JLCPCB


STORY:

mini_IMG_1595.JPG

In the past, I have worked on Power Delivering (PD) ICs. They are perfect for getting instant voltage from a GaN charger. I made a simple, standalone board that lets me select different PD voltages by charging the register settings. But why not keep it more usable with a single-button setup and a 10-cent microcontroller? No OLED displays, no rotary encoders, no over-engineering, just a simple toggle button with a few LEDs. That is exactly what I built in this project using the CH224K USB PD sink controller and the CH32V003F4P6, both made on WCH hardware.

The idea is straightforward. You plug in a USB-C PD charger, press the button to cycle through 5V, 9V, 12V, 15V, and 20V, and the corresponding LED lights up to tell you which voltage is currently active. I have designed the circuit in EasyEDA and manufactured the PCB with JLCPCB to realize this idea. In this session, I will share the code and hardware files so you can easily build your own. Let’s get started.

What Is USB Power Delivery?

mini_IMG_1594.JPG

Traditional USB ports give you 5V, and that is it. USB PD is a protocol that allows a charger and a device to negotiate higher voltages and currents over the USB-C cable. A typical USB PD charger can deliver 5V, 9V, 12V, 15V, and 20V, with a maximum power of 100W. This came into play when fast charging became popular in mobile phones: it is easy to charge at a higher voltage while fast charging because the mobile's internal circuits buck down and charge the battery at a higher current locally.

This negotiation happens over the CC (Configuration Channel) lines on the USB-C connector. The sink device tells the source what voltage it wants, and the source either agrees or offers the closest available option. This makes PD sink boards incredibly useful for makers and electronics hobbyists. Here, I have a dedicated chip for all this: CH224K. Based on three configuration pins (CFG1, CFG2, CFG3), it automatically negotiates the voltage with the PD source. You can use fixed resistor values, or you can drive the CFG pins with a microcontroller for dynamic voltage selection, which is exactly what I am doing here.

Key features of the CH224K:

  1. Supports USB PD 2.0 and 3.0 protocols
  2. Selectable output voltages: 5V, 9V, 12V, 15V, and 20V
  3. Three configuration pins (CFG1, CFG2, CFG3) for voltage selection
  4. Power Good (PG) output for voltage-ready indication
  5. Built-in CC pull-down resistors for USB-C detection
  6. Available in a compact SOP-10 package
  7. Operating supply voltage: 3.3V to 5.5V

Choosing a Microcontroller for Action

The main problem is the device's cost; I cannot pick a controller that exceeds the device's cost beyond the actual functionality cost. CH32V003F4P6 is WCH's ultra-low-cost $0.10 MCU. Despite the price, it is surprisingly capable for simple control tasks like this project.

Key specifications of the CH32V003F4P6:

  1. Core: QingKe 32-bit RISC-V2A, up to 48 MHz
  2. Flash: 16 KB
  3. SRAM: 2 KB
  4. Package: TSSOP-20 (20 pins)
  5. GPIO: 18 I/O pins
  6. Peripherals: USART, I2C, SPI, ADC, timers
  7. Operating voltage: 3.3V to 5V
  8. Cost: approximately $0.10

For this project, I am using 9 GPIO pins total. 3 for driving the CH224K configuration pins, 5 for the indicator LEDs, and 1 for reading the push button.

Circuit Diagram

Schematic_CH224K_CH32V003_2026-05-21.png

The USB Type-C connector is a standard 16-pin one. The VBUS pins carry the PD voltage from the charger, and the CC1/CC2 lines connect directly to the CH224K for PD communication. The three configuration pins CFG1, CFG2, and CFG3 are connected to the MCU's PD2, PD3, and PD4 pins, respectively.

There is a 10K pull-down resistor on the VDD line and decoupling capacitors to provide clean power to the IC. The PG (Power Good) pin of the CH224K directly drives LED2 through a 1K resistor. This LED lights up at the corresponding voltage at the output. I chose the LM317 because it can handle a wide input voltage range, which is important here since the VDD rail voltage varies with the selected PD voltage. Whether you have 5V or 20V on the input, the LM317 keeps the MCU supply at a stable 3.3V.

Note: Solder the MCU after configuring the LM317 potentiometer to 3.3V; otherwise, you will burn the MCU.

The firmware uses this exact table to set the correct voltage on each button press:

CFG1,2,3 and Voltage

1 - - 5V

0 0 0 9V

0 0 1 12V

0 1 1 15V

0 1 0 20V

PCB Design

Screenshot_2026_07_13-1.png
Screenshot_2026_05_21-1.png

I designed the PCB in EasyEDA as a compact two-layer board. The layout is organized with the USB-C connector on the left, the CH224K and supporting components in the center, and the indicator LEDs, button, and output terminal on the right. The board has silkscreen labels for the voltage levels 5, 9, 12, 15, 20 next to each indicator LED.

The Power Good LED and the "PD ON" label are placed near the CH224K section. For the power traces carrying VBUS current, I used wider traces to handle the higher currents that USB PD can deliver (up to 5A at 20V, that is 100W). The LM317 regulator is positioned centrally with adequate copper area for heat dissipation. I get 5 of them fabricated from JLCPCB because they offer the best price-to-performance. I never had a problem fabricating the PCBs from JLC. Get your fabrication files(Gerber/BOM/CPL) from here and get started.

Firmware Explanation

The firmware for the CH32V003 is written in C using WCH's MounRiver Studio IDE and their standard peripheral library. The code is clean and minimal. It does exactly three things: read the button, update the CH224K configuration pins, and light the correct LED. Let me walk you through each part.

GPIO Configuration: The `GPIO_Config()` function configures all required pins. First, we enable the peripheral clocks for Port C and Port D:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);

Then we configure PC0 through PC4 as push-pull outputs for the LEDs, PC5 as a floating input for the button (since we have an external 10 kohm pull-down resistor via R2), and PD2-PD4 as push-pull outputs for the CH224K CFG pins. At the end, we set all LED pins HIGH to turn them off (remember, active-low configuration).

Lookup Tables for Configuration: This is the clever part. Instead of using a big switch-case block, I created three lookup tables that map the mode index (0 through 4) directly to the CFG pin states:

static const uint8_t cfg1_table] = { 1, 0, 0, 0, 0 };
static const uint8_t cfg2_table] = { 0, 0, 0, 1, 1 };
static const uint8_t cfg3_table] = { 0, 0, 1, 1, 0 };
static const uint16_t led_table] = { LED_5V, LED_9V, LED_12V, LED_15V, LED_20V };

Mode 0 is 5V (CFG1=1, don't care about CFG2/CFG3), mode 1 is 9V (all zeros), mode 2 is 12V (CFG3=1), mode 3 is 15V (CFG2=1 and CFG3=1), and mode 4 is 20V (CFG2=1, CFG3=0). The `led_table` maps each mode to its corresponding LED pin. This approach is much cleaner than a switch-case and makes it trivial to add or reorder modes.

Setting the CFG Pins: The `setCFG()` function takes three arguments (one for each CFG pin) and drives the corresponding GPIO pins HIGH or LOW:

static void setCFG(uint8_t c1, uint8_t c2, uint8_t c3)
{
if (c1) GPIO_SetBits(GPIOD, CFG1_PIN);
else GPIO_ResetBits(GPIOD, CFG1_PIN);
if (c2) GPIO_SetBits(GPIOD, CFG2_PIN);
else GPIO_ResetBits(GPIOD, CFG2_PIN);
if (c3) GPIO_SetBits(GPIOD, CFG3_PIN);
else GPIO_ResetBits(GPIOD, CFG3_PIN);
}

This is straightforward GPIO manipulation using WCH's peripheral library functions. `GPIO_SetBits` drives the pin HIGH, and `GPIO_ResetBits` drives it LOW.

Applying the Current Mode: The `applyMode()` function ties everything together. It first turns off all LEDs by setting their pins HIGH, then calls `setCFG()` with the values from the lookup tables for the current mode index, and finally turns on the correct LED by pulling its pin LOW:

void applyMode(void)
{
GPIO_SetBits(GPIOC, LED_ALL); / All LEDs off /
setCFG(cfg1_tablemodeIndex],
cfg2_tablemodeIndex],
cfg3_tablemodeIndex]);
GPIO_ResetBits(GPIOC, led_tablemodeIndex]); / Active LED on /
}

This function is called both at boot (to set the initial 5V mode) and whenever the button is pressed.

Button Reading with Debounce: The `readButton()` function handles the push button with rising-edge detection and debouncing:

void readButton(void)
{
uint8_t btn_now = GPIO_ReadInputDataBit(GPIOC, BTN_PIN);
if (btn_now == Bit_SET && btn_prev == Bit_RESET)
{
Delay_Ms(20); / debounce /
if (GPIO_ReadInputDataBit(GPIOC, BTN_PIN) == Bit_SET)
{
modeIndex++;
if (modeIndex >= 5)
modeIndex = 0;
applyMode();
}
}
btn_prev = btn_now;
}

Here is how it works. The function reads the current button state and compares it with the previous state. If the button just went from LOW to HIGH (a rising edge), we wait 20 milliseconds for debounce, then read the button again. If it is still HIGH after the debounce delay, we know it is a genuine press. The mode index increments and wraps back to 0 after reaching 4, cycling through 5V, 9V, 12V, 15V, and 20V in order.

Main Loop: The `main()` function is simple. It initializes the system clock, delay timer, and GPIO pins, then sets the default mode to 5V (mode index 0) and enters an infinite loop that continuously polls the button:

int main(void)
{
SystemCoreClockUpdate();
Delay_Init();
GPIO_Config();
modeIndex = 0;
applyMode(); / Boot into 5V -- the safest default /
while (1)
{
readButton();
}
}

I deliberately chose 5V as the default boot voltage because it is the safest. Every USB PD charger supports 5V, and it will not damage any connected device. You can then press the button to step up to higher voltages as needed.

Power Good LED

One detail I really like about this design is the Power Good LED (LED2). This LED is not controlled by the microcontroller at all. It is directly connected to the CH224K's PG (Power Good) output pin via a 1K resistor (R11).

When the CH224K successfully negotiates the requested voltage with the PD, the PG pin goes HIGH, and LED2 lights up. If the negotiation fails, the PG pin stays LOW, and LED2 remains off. This gives you an independent, hardware-level confirmation that the output voltage is correct and stable. This is especially useful during development and debugging. If your voltage indicator LED shows 20V but the Power Good LED is off, you immediately know the charger could not deliver the requested voltage.

Testing and Working:

mini_IMG_1596.JPG
mini_IMG_1599.JPG
My Video.gif

After assembling one of them, I turned it ON! Using a USB meter in series with the charger. This will help to monitor the current and provide overcurrent protection. So if anything goes wrong, the circuit will automatically power down.

Image 1:

Image 2:

After clearly getting it worked and measuring the idle current, I switched to the charger directly.

Image 3:

Then connected a multimeter in voltage mode, and here are the results.

Outro

mini_IMG_1609.JPG
mini_thumb2.JPG

This USB PD Voltage Selector project combines two excellent WCH chips. With a single button press, you can cycle through 5V, 9V, 12V, 15V, and 20V, with clear LED indication of the selected voltage and hardware-driven Power Good confirmation. The total cost of this board is remarkably low, thanks to the CH224K and CH32V003 being among the most affordable chips in their respective categories. The firmware is simple, open-source, and easy to modify if you want to add features like EEPROM-based last-mode memory or a long-press to jump directly to a specific voltage. The hardware is minimal, and all the components are easy to source. This project helps you build your own USB PD voltage selector. If you have any questions, suggestions, or ideas for improvements, please comment below. Happy building!