PVI-xxx Inverter DSP Reveng

hardware

PVI-xxx Inverter DSP Reveng

August 17, 2025

Intro

This is part 3 of my inverter adventure. In part 1, we did a high-level teardown of a grid-tie inverter - the “big picture” view of where the power flows and what the major building blocks look like. In part 2 we took a look under the hood and the microcontroller that conducts the orchestra.

This time we’re taking a look at the Digital Signal Processors (DSP) that are responsible for generating the precise waveforms required to extract energy from the solar panels and feed it to the grid.

After the usual safety warning we’ll explore:

  • The high level functionality of the DSPs
  • Some more hardware reverse engineering of the programming interfaces.
  • Creating a bootloader payload and host script to extract the firmware.
  • Creating a disassembler
  • Creating a Ghidra processor module
  • And finally some firmware reverse engineering

Safety Warning (IMPORTANT)

Grid-tie inverters contain lethal voltages in many areas. Both high-voltage AC and DC are present on multiple internal rails, and disconnecting the unit does not automatically make it safe.

Large internal capacitors can retain 300 - 400 VDC for minutes after shutdown. There’s more than enough energy in those to kill you. These voltages are found on the MPPT input, the bulk DC bus, and the inverter H-bridge, and they’re spread across multiple PCBs.

If you haven’t worked with power electronics before, or you haven’t developed the instinct to spot something dangerous, this is not the project to start on.

This post is not how to repair your own unit. Read at your own risk!

Overview

DSP position in architecture

The DSPs are specialised CPUs that are optimised for performing real-time digital signal processing. In addition to the usual opcodes and peripherals found in general purpose microcontrollers, the DSP devices have dedicated maths processing hardware such as MAC pipelines and enabling them to processing digital signals in a fast, deterministic manor.

The inverter has two such DSPs.

  • MPPT DSP - runs the Maximum Power Point Tracking algorithm, directly driving the MOSFETs of a boost converter to extract optimal energy from the solar array.

  • Inverter DSP - samples grid frequency and generates a matched signal to drive IGBTs that feed current into the grid.

Both DSPs independently perform grid-disconnection safety checks. If either one detects an out-of-bounds parameter, it can cut the grid connection immediately.

Communication with the central microcontroller happens over SPI, which continuously polls each DSP.

The programming interface hardware

Each DSP has its own externally accessible RJ45 programming port hidden behind a removable cover.

Programming port location

Through hardware reverse engineering, I mapped out the pinout:

Programming port pinout

Programming is achieved via a 3.3 V TTL UART. To gain access, the following pins must be grounded before power-up:

  • nBOOT_EN: sampled at boot. If low, the DSP enters bootloader mode.

  • MOSI: sampled by the bootloader. If grounded, it enters serial programming mode. (If left high, it enters SPI mode, which isn’t compatible with this port.)

  • N_ENABLE: re-routes the required signals to the programming port, away from the rest of the design.

No specialist hardware is required, just a cheap USB 3.3 V TTL serial adapter and a simple custom cable:

Picture of programming cable

To enable flash writes, 5 V must be applied to the Vccp pin. Since we’re only reading, I left it disconnected to avoid accidental overwrites.

Bootloader operation

Synchronisation

On startup, the bootloader waits for a 0x0D character on its serial port which the bootloader uses to set its baud-rate parameters. The host must send it repeatedly until the bootloader replies with an acknowledgement. This “autobaud” mechanism should support multiple speeds, but in practice only 38400 baud was reliable.

After sync, the bootloader expects a payload, which it loads into RAM and executes immediately.

Bootloader payload format

Each field is 16-bit, little-endian. The length field = number of 16-bit words minus one.

Constructing a payload

To test the bootloader, I wrote a simple Python script:

import serial
import sys

ser = serial.Serial("/dev/ttyUSB0", 38400, timeout=0.2)

print("Waiting bootloader...")
while True:
    ser.write(b"\x0d")
    v = ser.read(100)
    if v and v[0]==0xAA:
      break
    elif v:
      print(f"Unexpected response. Aborting!", v)
      sys.exit(-1)

print("Synchronised!")

So far, so good. But what payload should I send it?

My search for a toolchain first led me to TI’s official tools, but support for this device family was pay-only. Eventually I found libasm, an assembler for old CPUs including this family. Along with a header file (f2407.h) of register definitions, I had enough to start.

First payload (and failure!)

My first payload looked like this:

.include "examples/f2407.inc"

.org 0FF00h
BOOM:
    LACC #2ah     ; Load literal * character into ACC
    LDP #DP_PF1   ; Set data page to the one containing the serial port
    SACL SCITXBUF ; Send character
send_done:
    BIT SCICTL2, BIT7 
    BCND send_done, NTC
    goto BOOM

I loaded this with my Python script and… nothing. No reply.

Debugging revealed the bootloader echoed extra bytes back, proving it was running and had not transferred control to the payload. Suspecting an off-by-one somewhere I adjusted the script to send an additional dummy word and it replied with a short row of * characters.

Turns out that the bootloader expects one more word than ’length’ and length is in words (not bytes as in the documentation.)

Firmware dump success

With stable code execution, I wrote a payload to:

  • Re-enable flash access
  • Unlock the Code Security Module
  • Dump the entire flash via serial
  • Keep resetting the watchdog

Luckily, the manufacturer hadn’t set a custom security password. The entire firmware dump came back clean:

Picture of flash dump win

The same process worked on both the inverter DSP and the MPPT DSP.

Quick inspection with libasm confirmed the dumps were valid firmware images.

Reverse engineering

Disassembly

libasm’s disassembler works, but it’s minimal. It’s fine for listings but not so great for analysis.

I turned to Ghidra, but it had no support for this DSP family. So I wrote my own disassembler, then decided to go further:

A Ghidra processor extension module

Ghidra is an NSA-developed reverse engineering suite. With the right processor module, it can produce both disassembly and C-like pseudocode along-side it.

To add support for the TMS320 C24x, I:

  • Used ChatGPT + OCR on TI’s datasheet to build a JSON opcode table (cleaned up manually).
  • Wrote a script to convert JSON into Ghidra’s Sleigh constructors.

Example JSON output :

"ABS": {
    "description": "Absolute value of ACC",
    "variants": [
        {
            "words": 1,
            "cycles": 1,
            "opcode": "1011 1110 0000 0000"
        }
    ]
},
"ADD": {
    "description": "Add to ACC",
    "variants": [
        {
            "words": 1,
            "cycles": 1,
            "opcode": "0010 SHFT IAAA AAAA",
            "note": "shift 0 to 15, direct or indirect"
        },

This table-driven approach let me bootstrap a Ghidra module quickly. After tweaking branches and addressing, I had full disassembly output in Ghidra:

Ghidra win

Symbols from f2407.h were imported too, and instructions augmented to understand branching and the data-page’s role in memory access.

Resources

Next steps

At this point, I now have:

  • The ability to load and run code on all three inverter CPUs (UI micro + both DSPs)
  • Full firmware dumps from all CPUs
  • A Python C24x disassembler and a working Ghidra processor module

Coming next:

  • Publish the tools
  • Deeper firmware analysis in Ghidra
  • And maybe… the takeover: converting it to an offline, non-grid-tied inverter running custom firmware.

Comments