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. AN 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,

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.

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:

Next try pinging it:

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.

17 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?


Leave a Comment

Your email address will not be published. Required fields are marked *