OpenDPS Design

This is the second part about hacking the DPS5005. Part one covers the reverse engineering of the DPS5005 and part three covers the process of upgrading stock DPS5005:s to OpenDPS.


The goal of OpenDPS is to reflash the stock DPS5005 (and friends) with a firmware that has the same functionality, has a less cluttered user interface and is remote controllable via wifi or a serial port. The application needs to respond to user input both via buttons and the serial port and also via ADC readings telling us if the current draw is larger than what we allowed. Buttons and UART RX are handled using interrupts and obviously the ADC needs to be interrupt based too for fast response (and less smoke). Responding to user input and TFT drawing can be left to the application context.

System Overview

The following modules are used in OpenDPS:

  • event – uses a ring buffer for storing events from the interrupt context to be handled in the application context. Events are button presses, received bytes on the UART and over current protection triggers
  • hw – the hardware abstraction (ADC, GPIO, …)
  • ili9631c – the TFT driver
  • opendps – main application
  • past – for storing persistent parameters in flash
  • protocol – helper for the serial protocol to instrument the OpenDPS device
  • pwrctl – power control, the DAC and calculations to convert ADC readings to eg. current draw
  • ringbuf – a ringbuffer implementation
  • spi_driver – just that
  • tft – TFT utility functions
  • tick systick handler with 1ms resolution
  • uframe – framing of serial protocol spoken
  • ui – handle the user interface

Control Flow

The application in opendps.c sits in a busy loop waiting for events to arrive in the circular buffer. Button presses, serial RX and over current protection events are placed in this buffer. The application calls the UI module that updates the UI with fresh measurements every 250ms. User input is also handled in the UI module. Events are 16 bit integers encoding the event type and optional event data. For the UART RX event the data obviously is the received byte. For button presses we also include information telling us if the press was a long press.

ADC and DAC Management

As I did not have the schematics at hand I had to reverse engineer the formula to calculate voltage/current based on ADC readings. I did some measurements and plotted in a Google Docs spreadsheet to find out

  • V_in(ADC1_IN8) – given a reading on channel 8, what is the input voltage?
  • V_out(ADC1_IN9) – given a reading on channel 9, what is the output voltage?
  • V_out(DAC) – what is the output voltage given a DAC setting? (and the inverse)
  • I_out(ADC1_IN7) – given a reading on channel 7, what is the current draw? (and the inverse)

To reduce complexity in the ADC IRQ handler, I pre calculate the ADC value that corresponds to the dialled current limit to quickly determine if we went over the limit. Here I ran into some issues as the ADC reading of the current draw differed between the two ‘5005s I had at hand. One of the units reported the wrong current draw which was tracked down to a different value when no current was drawn at all. The remedy is to take the first 1000 samples at system startup to calculate the ADC value for 0.00mA current draw and compensate in the calculations (see adc_i_offset & friends in hw.c)

Additionally, I currently collect a number of “over current samples” before triggering the OCP. This scheme needs some investigation.

Persistent Storage

User settings need to be persisted and this is taken care of by the past (parameter storage) module. This module uses two blocks for storing data. One is the “current block” and when it gets full a garbage collection is performed and the data is copied to the other block. Counters tell which block is the most recent one. In theory, power loss should not lead to corruption and/or data loss.


An anti aliased rendering of Ubuntu Condensed is used in two different sizes for the UI. The font is found in gfx/fonts and consists of two files. The glyphs itself (eg. ubuntu_condensed_48.png) and one image describing the widths of each character (eg. ubuntu_condensed_48_width.png).

Ubuntu Condensed, 48
Ubuntu Condensed, 48

The widths are indicated by a white pixel in the first row:

Width markings
Width markings

The fonts are converted by ‘make fonts’ which invokes The glyph PNG is converted to BGR565 (used by the TFT) and written to font-X.[h|c]. Each glyph is converted to a separate blob which speeds up blitting to the TFT. See tft_putch(…) in tft.c for usage. Blocking DMA is used to transfer the glyph data. One enhancement would be to have a list of DMA descriptors and fire the next DMA job in ISR context at the completion of the previous one. The icons displayed are handled in a similar manner (see eg. wifi.h) and are converted by ‘make graphics’. You should be able to change the font with little difficulty, in theory πŸ™‚

Remote Control

The OpenDPS device can be controlled via the UART port and you can either connect an FTDI adapter or an ESP8266. The latter is the most fun. A simple serial protocol is used (see protocol.h and uframe.h). Either way you go, there is a Python script to talk to the OpenDPS device,

% -h
usage: [-h] [-d DEVICE] [-s] [-u VOLTAGE] [-i CURRENT] [-p POWER]
 [-P] [-L] [-l] [-S] [-j] [-v]

Instrument an OpenDPS device

optional arguments:
 -h, --help show this help message and exit
 -d DEVICE, --device DEVICE
 OpenDPS device to connect to. Can be a /dev/tty device
 or an IP number. If omitted, will try the
 environment variable DPSIF
 -s, --scan Scan for OpenDPS wifi devices
 -u VOLTAGE, --voltage VOLTAGE
 Set voltage (millivolt)
 -i CURRENT, --current CURRENT
 Set maximum current (milliampere)
 -p POWER, --power POWER
 Power 'on' or 'off'
 -P, --ping Ping device
 -L, --lock Lock device keys
 -l, --unlock Unlock device keys
 -S, --status Read voltage/current settings and measurements
 -j, --json Output status as JSON
 -v, --verbose Verbose communications

The utility is agnostic as to how the OpenDPS device is connected. Provide an ip address and it will attempt to talk to that. Provide a TTY device and it will attempt to talk to that. For wifi connected DPS:es, you can provide the option –scan to find all OpenDPS devices on your network.

Any good old ESP8266 board with the UART exposed will work. Connect GND, RX and TX, build and flash esp8266-proxy (don’t forget to set your wifi credentials) and you should be good to go.

git clone
cd esp-open-rtos
git submodule init
git submodule update
git checkout -b netif remotes/origin/sdk_system_get_netif
export EOR_ROOT=`pwd`
echo '#define WIFI_SSID "my ssid"' > include/private_ssid_config.h
echo '#define WIFI_PASS "my secret password"' >> include/private_ssid_config.h
cd /path/to/esp8266-proxy
make && make flash

Note that you currently cannot use the master branch on the main ESP Open RTOS repository as my PR for a function needed for multicast has not been merged yet.

The design of esp8266-proxy is quite minimalistic. It receives UDP packets on port 5005, sends the content on the serial port and returns the answer to the UDP source address and port.

When OpenDPS starts the wifi icon is flashing at 1Hz. When it goes steady your ESP8266 has connected to your wifi and told the OpenDPS that it is connected. Try scanning for it:

% --scan
1 OpenDPS device found

Next try pinging it:

% -d --ping

The TFT should flash once as a visual indication. If you get ‘Error: timeout talking to device’, check if you swapped RX and TX. You can always connect an FTDI adapter on either RX pin on the ESP8266 and OpenDPS to debug the communication.

That concludes part two, please see part three for a description of how to upgrade your DPS5005 to an OpenDPS 5005.

36 thoughts on “OpenDPS Design

  1. Pingback: Open Source Firmware For A Cheap Programmable Power Supply | Hackaday

  2. Code and Solder

    I have some questions:
    1. Does the output quality (noise etc.) change with this firmware?
    2. Could you add output power back to the display? It is useful to have it in some cases.
    3. It seems Constant Current mode is not included. Was this also the case in the original firmware? It seems to be listed on some sites.
    4. Do you have anything against me making a video about the project and update process (with proper sources of course)?

    I love the project, I was actually pretty annoyed the DPS**** series lacks computer control capabilities around a week ago.

    1. Johan

      1. I would guess not. I have not had the time to look at the noise but plan on doing this once I build the DPS into a nice enclosure (where there would be room for caps, ferrite cores and a regulator for the ESP).
      2. That is definitely doable, would have to reduce the size of the font though.
      3. I think CC was in the original firmware. I chose not to include it as I am s software guy who only needs 3.3V or 5V with a limited current πŸ™‚
      4. That would be way cool, I’d love to see it!

  3. Robert

    Hi, I saw this great project on hackaday. Congrats! Constant Current mode and displaying the power output would be quite nice. By using constant current with a set maximum voltage this thing could be used as a quite nice battery charger or LED driver. CC mode is really helpfull.

    1. Johan

      Thanks! I omitted CC to save time. Chances are slim I will do the CC implementation though as I neither charge batteries nor drive a great many LEDs πŸ˜€

  4. Tapsa

    Any ideas how big work it will be implement mWh “trip meter”? Developing iot things many times you want know how much power this take with one wake up period. Sampling it with quite fast rate would give really useful information how fast your device will drain battery.

    1. Johan

      Actually, the single most important measurement is your sleep current as your IoT node will sleep for most of its life. Then we are talking micro amps and the resolution of the DPS probably is not sufficient for this. The EEVblog uCurrent is perfect for this though.

  5. Smith

    This is fantastic work.

    Can we use it in some way so that the functionality remains with the added benefit of being able to remote control it?


  6. Smith

    Hi Johan,
    What do you mean no serial interface? here is what you wrote:

    “The goal of OpenDPS is to reflash the stock DPS5005 (and friends) with a firmware that has the same functionality, has a less cluttered user interface and is remote controllable via wifi or a serial port.”


  7. Smith

    Sorry for my ignorance. πŸ™

    So if both the hardware and OpenDBS has serial interfaces what is the reason that it can not be remotely controlled?


  8. Thomas Jansen

    Really good worl!
    I just got 1 question: You know, how the TFT is connected? (IΒ²C, 2/3WireSPI?)
    8 Pins – 2 supply -1 NC – 1 LED still 4 to go….

  9. Eric Smith

    Is it possible that the adjustable current limit of the original product uses another DAC to set the limit? Ideally you don’t want to depend on the processor for current limit, as that would allow the load to go overcurrent for several or many microseconds before the firmware responds, while in a normal power supply with current limiting, it is done by direct feedback to the regulation circuitry, and operates much faster, limited mainly by the loop bandwidth of the regulator.

    Were I designing such a product, I’d use another DAC to control the current limit, but in a product so inexpensive, I suppose all bets are off.

  10. SkinnyV

    Awesome work! I am really excited to receive my DPS5005 so I can try out your firmware. The interface does look much cleaner and sleek. Kudos on your hard work, it will be very useful to people building adjustable power supply. Thanks for sharing!

    1. Johan

      Thanks! There is cc support on the cc branch of the GitHub repo. This branch and master have diverged somewhat and I should pull them together when time permits.

  11. Chaveiro

    Just discover this firmware you make, great job!
    I’ve a few suggestions for evolution:
    1 – Add CC (seems already on going)

    2 – Add a simple graphical scope of mA vs time or W vs time, for the last minutes and give statistics about it since last user reset. This is great for profiling power consumption on low power circuits that keep sleeping for the most time.

    3 – Add a tools menu where the user could select a battery charger option: ex select PB/NiMH/Li-Ion, num of cells in series to charge, cell capacity in mAh, max time until power off, all user configurable. And also detect full charge by monitoring the charge current profile for the selected chemistry.

    4 – Is the hardware able to emulate via firmware a load charge ? If so that is my other suggestion.

    1. Johan

      Thanks! #2 is a cool idea. I’ll leave #3 to someone charging batteries and raising pull requests πŸ˜‰ Not sure what you mean with #4 though.

  12. luc


    I’m tying to communicate with my DPS5020 which has a USB connection with my ODROID C2 – Ubuntu 16.04
    – python3 but have some problems with the instruction ~$ pip install protocol and uframe
    Can you help me out ? I get the error : Could not find a version that satisfies the requirement

    1. Johan

      AFAIK the original FW is nowhere to be found. Since read out protection is enabled on the STM32, RD Tech seems like they don’t want the FW floating around.

  13. Ben Ward

    For anybody interested in a more complete product with enclosure and 230V AC to 50V DC power supply included I bought one of these from AliExpress:

    I have done a quick tear down and the enclosure seems like good quality and the AC to DC powersupply seems acceptable. I’m still waiting for my ESP8266 board to arrive (with support for external antenna as the enclosure is metal) and a ST-link2. One nice thing about the metal enclosure is that there is a micro USB port hole without any female micro usb connector pcb – this could be repurposed as a reprogramming port.

Leave a Comment

Your email address will not be published.