mensi.ch

Creating an USB interface for SunnyBoy Inverters

Previously, I looked at the data interface of a SunnyBoy inverter. Since we now know the pinout, we can attempt at building our own interface board. To make our life simple, it would be nice to get it to be USB, so we don't have to mess around with RS485 or RS232 adapters.

Considerations for the USB Interface

USB uses differential signalling at a speed at depends on the version. While the inverter itself uses a relatively slow baud rate, our electrical interface still needs to be good enough for USB. If we want to use the existing screw terminals to connect the wires, USB3 is not going to work, so we want to aim at an UART to USB converter IC that uses as old or slow of an USB version as possible.

The popular cheap option - the CH340 series - is an USB full speed device, so USB 1.1 with 12 Mbps. Maybe we can get away with the screw terminals. Otherwise we have to use a proper USB connector.

Since USB also provides 5V power, we do not need an isolated DC-DC power supply. Instead, we can just use the power provided by USB.

Isolation

The manufacturer-designed PiggyBack modules use a combination of ESD protection diodes, resistors and optocouplers to isolate the data signals. Another option is to use dedicated digital isolation ICs. We however have to be a bit careful about what part we select. The cheap, widely available SOIC-8 parts often have weak or no input protection and a 560V rating for the working voltage. They tend to have a higher peak isolation voltage rating, but can only survive that for short amounts of time. We need more than 600V isolation sustained.

There are also higher rated parts with integrated ESD protection, for example the TI ISO7721DWR. It comes in a wider package to also increase the creepage distances around the body of the part. It only needs one external part: A 0.1 uF capacitor on the power pins for each side.

Circuit

So, given the choice of digital isolator and TTL-to-USB converter IC, we can put together a simple circuit based on the recommended external parts in their respective datasheets:

Since the ISO7721DWR already has ESD protection integrated, we do not need external resistors or ESD diode arrays. A 100nF decoupling capacitor is recommended on each side though.

Likewise, the CH340N includes an oscillator and all required resistors for USB, so there we also get away with just adding some power supply decoupling.

PCB

Due to the low part count, the PCB is quite trivial:

The only slightly noteworthy thing is that the ground planes on the back have a nice separation gap between them. Otherwise we'd be circumventing the isolation the ISO7721DWR provides. Even with the solder mask, the isolation should be good enough for 1000V.

Getting the Boards built

I like the integration between lcsc.com, easyeda.com and jlcpcb.com for quick prototypes - you get the symbols and footprints directly via the part-number and ordering PCBs with or without assembly is just a few clicks. So I used them here as well - you could of course achieve the same with KiCad and some other manufacturer.

At the time of writing, 5 PCBs cost $3.10 and parts for 4 boards $10.69. Shipping and handling for both (old customers get a discount on LCSC when also ordering from JLCPCB) came out to $11.56. This is quite a bit less than what even a single second-hand RS485 piggy back sells for.

I also ordered some insulating silicone tube and cable glands which came out to ~$5 per inverter.

Testing

Warning!

If you're following along and want to install a similar board, please keep in mind that the inverter has mains voltage and the full PV string voltage present. This can be hundreds of volts DC. Disconnect the inverter completely and let capacitors drain. If in doubt, hire a certified electrician to do the work.

Also, keep in mind that the design presented in this article is the work of an amateur and has not been tested or certified by any authority or lab. I cannot guarantee that it is safe and does not burn down your house.

With the parts soldered, we're ready to test if the gamble with USB 1.1 over the screw terminals worked out. While just chopping off one end of an old USB A to mini or micro USB cable would be enough, I'd recommend putting ferrules on the exposed ends. Otherwise the screw terminals can easily break the thin strands for the D+ and D- signal wires.

USB can be tested even with the inverter turned off - since the required power is provided via USB as well. And it indeed looks like the CH340N works just fine hooked up to a Raspberry Pi.

With everything closed up and the inverter powered up, we can try to read out some values. SMA provides an open source library to interface with their devices called YASDI. After building the library with the instructions supplied with the SDK, we can use the yasdishell executable to test. For that, we first need to create a yasdi.ini config file:

[DriverModules]
Driver0=yasdi_drv_serial

[COM1]
Device=/dev/ttyUSB0
Media=RS232
Baudrate=1200
Protocol=SMANet

[Misc]
DebugOutput=/dev/stdout

It does not seem to make much of a difference whether we use RS232 or RS485 as the media. In yasdishell, scan for devices and get spot values as per the built-in help.

Conclusion

With this, we have a working, simple way of getting stats out of old inverters. To process the data further, one can for example use github.com/pkwagner/yasdi2mqtt to publish measurements to MQTT. I'm then using my InfluxDB-Telegraf-Grafana stack I use for other things to also store and visualize the inverter data.


Data Interface of an SMA SunnyBoy Inverter

We have a few old SMA SunnyBoy solar inverters I'd like to interface with - so after some research, it looks like these can be interfaced with different types of technologies (including RS232 and RS485). The way this is achieved is via "PiggyBack" modules that are installed in the inverter to provide one of these interface types.

Warning!

Before we proceed further with poking at the hardware, please keep in mind that these inverters have both mains voltage and the string voltage of your PV panels present. This voltage can easily be hundreds of volts DC! The connection between inverter and panels does not have an RCD. The panels will happily push current through you as long as the sun shines.

It is absolutely crucial that you completely disconnect the inverter from both mains and PV panels before even thinking about opening it. Additionally, the inverter has very large capacitors that need to discharge. Follow the instructions provided in the service manual. Do not open the inverter if you don't know what you're doing - get a certified electrician to help.

Taking a closer look at an RS485 PiggyBack

While one can find pictures on the internet and infer some information from installation manuals, it doesn't compare to having one in your hands. I luckily found an RS485 PiggyBack for a reasonable price on ebay and it even included all accessories and manuals.

Here it is:

The construction is clearly built around a large isolation gap in the middle, where we only have optocouplers and a transformer going across. There's even a slit below the optocouplers and the copper layers are pulled back.

On the upper side (with the 2*7 pin header), we have two large ICs:

  • a MAX253 transformer driver for isolated power supplies designed for isolated RS485 interfaces
  • some variant of an AHCT14 inverter with Schmitt-Trigger inputs

There are also two SRV05 ESD protection diode arrays (the small 6 pin ICs) and some passives. This looks very much like the "hot", inverter side.

On the bottom side (with the 2*5 pin header), there are also 2 large ICs:

  • a MAX487E RS485/RS422 transceiver
  • an ST KF50 low drop voltage regulator

We also have a couple of diodes and larger capacitors - these are part of the secondary side of the transformer isolated power supply. They pretty much follow the example circuit from the datasheet of the MAX253.

In general, it looks like this board is designed to take the interface and power from the inverter, isolate it and provide RS485 on the bottom header.

Where does it all plug into?

The inverter has the matching headers to plug it in:

We can again see that the upper part is connected to the rest of the inverter, while there is a large isolation gap around the lower header.

The screw terminal is labelled "2 3 5 7" and is used to connect the RS485 interface. The jumpers next to it can be used to enable 680Ω pull ups / downs and a 120Ω termination resistor. The 680Ω resistors can be seen on top of the header. The 120Ω resistor is not present on the inverter side (but can be found on the PiggyBack)

We can get the screw terminal assignment from the installation manual. The positions are used differently if we compare the manuals from the RS232 and RS485 PiggyBacks. The connections between screw terminal and pin header are quite easy to determine with a multimeter:

2 3 5 7
RS485 DATA+ GND DATA-
RS232 RX TX GND

Some pins seem unconnected - but based on installation manuals, it looks like other devices also supporting PiggyBacks have wider screw terminals. Maybe these pins are connected in those devices.

Just visually speaking, it looks like screw terminal positions labelled 2 and 7, so the DATA signals for RS485 are best suited for differential signals, as they are of similar length and only 7 has a short (5mm-ish) stub to the side for the jumper.

It's also interesting how there's a mix of resistors on the inverter and on the PiggyBack - naively I would assume that you'd just want to have connections to the jumpers and then supply any required resistors on the PiggyBack. It could however be that either for space constraints or signal conditioning this arrangement worked better.

For the 680Ω pull up, pin 2 on the lower header has to be supplied with 5V from the LDO. To protect against improper installation, the pin is connected via two 100Ω resistors in parallel to limit the current as well as a diode to avoid reverse current flow.

Pinout of the Hot Side

Trying to figure out the pinout of the hot side, we immediately have obvious candidates for power and ground:

Pin 10 has a thicker trace than the others, and pin 9 is fully connected to something kinda ground-plane-looking. We can easily confirm with a continuity test to the power and ground pins on the MAX253 given in its datasheet.

Since I do not want to mess with my inverter under power, I don't know whether power is 3.3V or 5V. All ICs work with both voltages. Another trick to see which one works is to just power the board with 3.3V and seeing whether the secondary side gets enough voltage for the 5V LDO to work. Unfortunately the transformer is wound to give a slightly higher voltage on the secondary and both 3.3V and 5V create secondary voltages acceptable for the 5V LDO.

For the other pins its best to work our way backwards. From the MAX487E we know which pin is what. We essentially have 4 signals:

  • RO: Receiver Output. This is the data received from RS485.
  • RE: Receiver Output Enable. Can be used to put RO into high impedance mode.
  • DE: Driver Output Enable. Has to be high to enable data transmission on RS485.
  • DI: Driver Input. This is the data sent to RS485.

Note that RE, DE and DI all have to be driven by the inverter side, while RO is the only signal in the other direction. This matches up with the optocouplers: 3 of them point from inverter to isolated side, while only one goes in reverse.

Following the signals, they all cross the optocoupler, pass through the Schmitt-Trigger inverters at least once, go through a protection resistor, pass by the ESD diode array and end up on the pin header. The mapping on the header ends up being:

  • RO: Pin 3
  • DE: Pin 4
  • DI: Pin 5
  • RE: Pin 7
  • GND: Pin 9
  • VCC: Pin 10

In terms of the other pins, it looks like some can optionally be connected via unpopulated resistor footprints. Only pin 13 is connected via a 0Ω resistor to an inverted version of DI. No idea what that's used for, maybe some feedback to see if the PiggyBack is present / working?

Mechanical Aspects

Finally, a short note on the mechanical aspects: The hole in the inverter housing seems to be sized for a PG16 cable gland. Since that is a bit wide for the cables used in RS485, a reduction to PG11 and a PG11 cable gland are actually used.

Since the cable is likely going to touch the main DC input assembly, a silicone tube is provided to add further insulation between the data cable and the rest of the inverter. Depending on the insulating material and thickness of the data cable you use, this could actually be necessary to provide the required insulation - after all, it would be a bit silly to add this many protection mechanisms on the PiggyBack to then have a poorly insulated data cable rest directly on the high voltage carrying parts.


Recovering an old Intel RST RAID0 Array

Many years ago, I was using a mainboard's integrated Intel Rapid Storage Technology feature to create a larger disk than was available for SSDs back then. I'd like to recover some data from them, so let's see what Linux can do.

Imaging the Disks

Whenever you work on data recovery, you really want to do everything you do on disk images instead of the live disks. It is too easy to mistype some command or get some unexpected software behavior, ruining your chances at recovery by writing over critical data.

Taking images is luckily trivial, you just need enough space:

# dd status=progress if=/dev/sda of=disk.img bs=1M

In this example, /dev/sda is the disk you want to image, and we set a blocksize of 1 megabyte. A larger blocksize than the default 512 bytes can make copying more efficient, depending on the type of disk etc.

I also took an image of the second disk, ending up with two files disk.img and disk2.img.

Exploring the Disks

Tools often expect blockdevices and not image files, so as a first step, let's create loopback devices for both:

# losetup --find --show disk.img
/dev/loop0
# losetup --find --show disk2.img
/dev/loop1

fdisk can already help us determine the order of the disks:

# fdisk -l /dev/loop0
Disk /dev/loop0: 238.47 GiB, 256060514304 bytes, 500118192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x1234abcd

Device       Boot     Start        End   Sectors   Size Id Type
/dev/loop0p1 *         2048     718847    716800   350M  7 HPFS/NTFS/exFAT
/dev/loop0p2         718848  999301119 998582272 476.2G  7 HPFS/NTFS/exFAT
/dev/loop0p3      999301120 1000222719    921600   450M 27 Hidden NTFS WinRE

# fdisk -l /dev/loop1
Disk /dev/loop1: 238.47 GiB, 256060514304 bytes, 500118192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

RAID0 works by interleaving chunks of data between the disks. Since the MBR with the partition table is quite small (512 bytes), it will entirely fit even in the smallest of stripe settings. For this reason, we only get a valid partition table on one of the disks, while not on the other.

We can also see that the second partition is actually larger than the single disk itself, further hinting at a RAID0 situation. It could of course be any sort of RAID technology but there should be further metadata about the configuration of the array. A first hint is this:

# blkid /dev/loop0
/dev/loop0: TYPE="isw_raid_member"

which is a string associated with Intel's Rapid Storage Technology (RST) or Matrix Storage Manager (MSM).

Trying mdadm

mdadm claims to support Intel RST, so let's give it a go:

# mdadm --examine -e imsm /dev/loop0 /dev/loop1
mdadm: /dev/loop0 is not attached to Intel(R) RAID controller.
mdadm: /dev/loop0 is not attached to Intel(R) RAID controller.
mdadm: Failed to retrieve serial for /dev/loop0
mdadm: Failed to load all information sections on /dev/loop0
mdadm: /dev/loop1 is not attached to Intel(R) RAID controller.
mdadm: /dev/loop1 is not attached to Intel(R) RAID controller.
mdadm: Failed to retrieve serial for /dev/loop1
mdadm: Failed to load all information sections on /dev/loop1

This is a bit disappointing, it seems like mdadm expects the array to be set up with the current mainboard, but we are looking at image files, not a live array. Consulting the internet reveals one can set an environment variable to tell mdadm to not expect a controller:

# IMSM_NO_PLATFORM=1 mdadm --examine -e imsm /dev/loop0 /dev/loop1
mdadm: Failed to retrieve serial for /dev/loop0
mdadm: Failed to load all information sections on /dev/loop0
mdadm: Failed to retrieve serial for /dev/loop1
mdadm: Failed to load all information sections on /dev/loop1

This is not really much better, but hey, we got a different error. The internet doesn't seem helpful in this case, so let's go poke a bit at the source code. The code is at on kernel.org but there also seems to be an older github mirror by the former maintainer. Since Github has some nicer code browsing features, let's use that.

Let's start by searching for the first error string: "Failed to retrieve serial for" which leads us to the promising sounding super-intel.c. The context around the line is this:

rv = nvme_get_serial(fd, buf, sizeof(buf));

if (rv)
    rv = scsi_get_serial(fd, buf, sizeof(buf));

if (rv && check_env("IMSM_DEVNAME_AS_SERIAL")) {
    memset(serial, 0, MAX_RAID_SERIAL_LEN);
    fd2devname(fd, (char *) serial);
    return 0;
}

if (rv != 0) {
    if (devname)
        pr_err("Failed to retrieve serial for %s\n",
               devname);
    return rv;
}

So it looks like mdadm is trying to read the serial number of the device it's looking at. This fails for loopback devices. But in the code, we can also see that there's a check for another environment variable to get around it: IMSM_DEVNAME_AS_SERIAL. Let's try that:

# IMSM_DEVNAME_AS_SERIAL=1 IMSM_NO_PLATFORM=1 mdadm --examine -e imsm /dev/loop0 /dev/loop1
/dev/loop0:
        Magic : Intel Raid ISM Cfg Sig.
        Version : 1.0.00
    Orig Family : c241033d
        Family : c241033d
    Generation : 00002613
Creation Time : Unknown
    Attributes : All supported
        UUID : 4de8c4a7:559e81a4:e0687ed8:b47e5ce8
    Checksum : 579a65d2 correct
    MPB Sectors : 1
        Disks : 2
RAID Devices : 1

[root]:
    Subarray : 0
        UUID : f9dd0ad6:7f914762:8b8feecb:32bda46b
    RAID Level : 0
        Members : 2
        Slots : [UU]
    Failed disk : none
    This Slot : ?
    Sector Size : 512
    Array Size : 1000226816 (476.95 GiB 512.12 GB)
Per Dev Size : 500113672 (238.47 GiB 256.06 GB)
Sector Offset : 0
    Num Stripes : 7814272
    Chunk Size : 32 KiB
    Reserved : 0
Migrate State : idle
    Map State : normal
    Dirty State : clean
    RWH Policy : off
    Volume ID : 0

Disk00 Serial : DISKSERIAL1
        State : active
            Id : 00000000
    Usable Size : 500107790 (238.47 GiB 256.06 GB)

Disk01 Serial : DISKSERIAL2
        State : active
            Id : 00010000
    Usable Size : 500107790 (238.47 GiB 256.06 GB)
/dev/loop1:
        Magic : Intel Raid ISM Cfg Sig.
        Version : 1.0.00
    Orig Family : c241033d
        Family : c241033d
    Generation : 00002613
Creation Time : Unknown
    Attributes : All supported
        UUID : 4de8c4a7:559e81a4:e0687ed8:b47e5ce8
    Checksum : 579a65d2 correct
    MPB Sectors : 1
        Disks : 2
RAID Devices : 1

[root]:
    Subarray : 0
        UUID : f9dd0ad6:7f914762:8b8feecb:32bda46b
    RAID Level : 0
        Members : 2
        Slots : [UU]
    Failed disk : none
    This Slot : ?
    Sector Size : 512
    Array Size : 1000226816 (476.95 GiB 512.12 GB)
Per Dev Size : 500113672 (238.47 GiB 256.06 GB)
Sector Offset : 0
    Num Stripes : 7814272
    Chunk Size : 32 KiB
    Reserved : 0
Migrate State : idle
    Map State : normal
    Dirty State : clean
    RWH Policy : off
    Volume ID : 0

Disk00 Serial : DISKSERIAL1
        State : active
            Id : 00000000
    Usable Size : 500107790 (238.47 GiB 256.06 GB)

Disk01 Serial : DISKSERIAL2
        State : active
            Id : 00010000
    Usable Size : 500107790 (238.47 GiB 256.06 GB)

Very nice! This information looks all nice and correct.

Mounting the Array

Let's try to assemble the array:

# IMSM_DEVNAME_AS_SERIAL=1 IMSM_NO_PLATFORM=1  mdadm --assemble --verbose --metadata=imsm /dev/md99 /dev/loop0 /dev/loop1
mdadm: looking for devices for /dev/md99
mdadm: /dev/loop0 is identified as a member of /dev/md99, slot -1.
mdadm: /dev/loop1 is identified as a member of /dev/md99, slot -1.
mdadm: added /dev/loop1 to /dev/md99 as -1
mdadm: added /dev/loop0 to /dev/md99 as -1
mdadm: Container /dev/md99 has been assembled with 2 drives

Unfortunately this only seems to assemble a container which doesn't look like the actual array. Searching the internet is not really giving me the answer here, and such an abstract topic seems tricky to figure out without a complete study of the mdadm source code.

But we have another trick up our sleeve. We can use the device mapper directly to set up a device as we have all the configuration values from mdadm --examine. We know /dev/loop0 comes first and that the chunk size is 32 KiB. The total size is 1000226816. This number is weird though, weren't we expecting around 500000000000 bytes? Turns out, this number is in 512 byte sectors, not bytes. The good thing is dmsetup also expects sectors, so we don't have to convert it:

# dmsetup create restore --table "0 1000226816 striped 2 64 /dev/loop0 0 /dev/loop1 0"

This creates a device mapper volume called restore (so will be found at /dev/mapper/restore). The --table argument contains the config for the volume. The pieces are:

  • 0 the start sector. We start at the beginning, so 0 it is.
  • 1000226816 the number of sectors of the complete device. Copied from above.
  • striped data is striped as this is a RAID0.
  • 2 number of disks.
  • 64 stripe size in sectors. (32*1024)/512 = 32*2 = 64.
  • /dev/loop0 the first disk.
  • 0 start with the first sector.
  • /dev/loop1 the second disk.
  • 0 start with the first sector.

We can use kpartx and ntfs-3g to discover the partitions (so we get /dev/mapper/restore2 like we would with /dev/sda2) and mount the filesystem:

# kpartx -a /dev/mapper/restore
# ntfs-3g -o ro /dev/mapper/restore2 /mnt/restore

And there we go, all the data is there, yay!

Final Thoughts

While this was a good learning experience, having figured out everything let's you do more targeted searches for the magic keywords. And of course it turns out Andrew Brampton went through pretty much the same journey. Oh well ;)