Friday, October 13, 2023

Calculating Copper Wire Characteristics

For the early of my life I've relied on tables or similar references to look up things like copper ampacities and resistance.  Now I just remember a few constants, and can calculate what I need to know.

The photo above is a 12 AWG copper wire, commonly used in building wiring in the US and Canada.  It has a diameter of .0808 inches or 2.052 mm.  When used for building wire, it is typically limited to carrying 20 amps of current by breakers or fuses.  For circuits carrying more current, 10 AWG wire with a diameter of 2.587 mm can be used.  For a change of 2 AWG in wire size, the change in diameter is always 1.26, and therefore the change in cross-sectional area is 1.26^2.

The cube of 1.26 is 2.0, so an increase in size of 3 AWG will double the cross-sectional area of the wire, and reduce the linear resistance by half.  The resistance of 10 AWG wire is 1 ohm per thousand feet (304.8 m), so the resistance of 16 AWG wire, often used in extension cords, is 4 ohms per thousand feet at room temperature.  The amount of heat generated in a wire is calculated with the formula P=I^2xR.  If 10 amps is flowing through 1000 feet of 16 AWG wire, the power dissipated will be 10^2 x 4 or 400 watts.  If the wire is 10 AWG, and the current is 20 amps, the power dissipated will be the same 400 watts.  However voltage drop will be lower, since V = I x R.  The resistance of 10 AWG wire is a quarter of 16 AWG, so the voltage drop in the 10 AWG wire with 20 amps will be half of the voltage drop of the 16 AWG wire with 10 amps.

The resistance of copper increases with temperature, by 0.393% per degree C, so increasing the temperature by 25 C will increase the resistance by almost 10%.  Calculating temperature increase due to power dissipation is quite complicated, so it is common in electrical codes to consider an ambient temperature of 30 C, and a temperature rise in the wire of no more than 30 C.  Because of that, most wire sold in Canada that is CSA certified will use insulation rated for at least 60 C.  The most common category of wire used in residential construction, NMD90, has an insulation temperature rating of 90 C.

On a final note, a wire labeled 16 AWG might not really be 16 AWG.  I generally trust the electrical distributors like Rexel and Wesco, however before using some battery wire from a discount hardware store I'd inspect it carefully first.

Saturday, May 13, 2023

MODBUS communication with Solis 4G-US inverters

Solis single-phase inverters have a circular RS485 connector supporting MODBUS communication.  The connectors can be difficult to find for sale outside China, so using a wifi data logger stick is a more straightforward way of communicating with the inverters.  IGEN Tech is the OEM for the Solis wifi data loggers, which IGEN also sells under the SOLARMAN brand.  While the same circular connector is used by many other inverter manufacturers such as Solax, RENAC, and KSTAR, the logger firmware is customized to read and report the MODBUS registers for a particular manufacturer.

The LSW-3 series of wifi loggers allow external programs to perform MODBUS queries via a TCP connection port 8899.  I believe this is a variant of the MODBUS/TCP protocol that is assigned TCP port 502.

To perform MODBUS queries, I used pysolarmanv5.  To connect to the logger pysolarmanv5 requires the logger serial number and IP address.  Initially I read the serial number off the label of the logger, and looked up the IP address from the admin page of my router.  Later I noticed solarman_scan.py, which sends a broadcast UDP packet which the data logger replies to.  I sometimes had to run it more than once before the logger responded to the scan packet.

The Solis 4G-US series inverters are Sunspec MODBUS certified, and have the well-known 32-bit ‘SunS’ identifier (0x53756e53) at address 40001.  This means it should be possible to read the registers by decoding the SunSpec information models and inverter device IDs.  SunSpec shares some example code, however I haven't been able to figure it all out.

I couldn't figure out the Solis registers using SunSpec, but I was able to find the register documentation from Ginlong.  The AC output power and DC input power are 32-bit registers at address 3005 using MODBUS function code 4 (input registers).  I wrote a python program to read the output and input power and calculate the efficiency.  It also reads the inverter temperature, and outputs the data every 5 minutes.  I also wrote a small AWK program to calculate the weighted average of multiple samples.  The code can be found in my github repo.

Over multiple days of output in the spring of 2023 including sunny and cloudy days, I observed an overall efficiency of 94% for a Solis 1P4K-4G-US.  For a Solis 1P6K-4G-US I observed an average efficiency of 95%.  This compares to respective advertised CEC weighted efficiencies of 97.5% and 97.0%.

Monday, April 10, 2023

KSTAR Single Phase String Inverters

 

KSTAR New Energy makes single phase grid-tied inverters ranging from 1 kW to 10 kW.  I tested a 3000S, a 5000D, and a 6000D that were produced in KSTAR's factory outside of Shenzhen.  Their single phase inverters are marketed for locations with a 230 V line to neutral (L-N) grid.  They also work with the split phase 240 V line to line grid that is typical in the US and Canada.  They do not have UL 1741 certification, so they would require special engineering approval to be used for permanent installations with most US and Canada power utilities.

Residential inverters used in the US and Canada usually have an attached junction box with terminal connections for DC and AC wiring.  In the rest of the world, inverters usually have MC4 connectors for the DC string input, and a watertight three-pin plug connection for the AC output.  It is much more convenient having the plug connections when testing inverters and PV panels.  It also avoids potential electrical code concerns when DC wiring up to 600 V and 240 Vac are in the same junction box.

 

The KSTAR inverters all included MC4 crimp connectors for terminating the DC strings.  The AC connector will accept SOOW or SJOW cable with a outside diameter of up to 16 mm.  I used 3-wire 12 AWG SOOW cable that is rated for up to 25 Amps.

The 3000S has a single string input, and a "nominal" output power of 3 kW.  It is a light inverter, with a stated weight of 8 kg.  Out of the box, the measured weight was 7.3 kg.  The light weight makes it very easy for a single person to install.  When hooked up to a test string of 10 72-cell panels, the efficiency was 85-86%. This is much lower than the spec efficiency of 97% or the 96% efficiency at nominal 380 V listed on the inspection and test sheet that was included with the inverter.  With input power of 3070 W and input voltage of 367.7 V, the output power was 2620 W, for an efficiency of 85.3%.  KSTAR sales and engineering were unable to explain the low efficiency.


The 5000D and 6000D have the same external dimensions and connections on the bottom.  The weight of the 5000D is 11.74 kg, while the 6000D weighs 12.48 kg.   This suggests the 6000D has different internal circuitry, likely larger inductors and capacitors, to support the higher power rating.

The efficiency of the 5000D and 6000D inverters ranged between 89 and 91%.  The screenshot of monitor data below shows a total input power of 6240 W with AC output power of 5570 W, for an efficiency of 89.3%.  This test was done with a large difference between the PV1 and PV2 voltages to represent typical residential PV installations which are not optimized for the inverter's 380 nominal string voltage.


The KStar inverters are reasonably priced and easy to install, but the low efficiency makes them unattractive compared to Growatt and Ginlong Solis inverters.

Wednesday, October 5, 2022

DC Wiring Losses in String and Microinverter Solar PV Arrays

There are two common ways of wiring solar PV arrays.  Each panel can be connected to a microinverter, with each microinverter connected in parallel to an AC bus.  Alternatively, panels can be connected in series, with one or more DC strings connected to an inverter.  Although there is debate over which design is best, at Solar Si, we prefer string inverters.  This is an analysis of DC wiring losses with an array of 8 72-cell LONGi PV modules of about 450 Watts each.

There are two sources of wiring resistance in the array.  The first is from the wire itself, and the second is from the connectors.  The 12 AWG wire used for the panel output cables has a resistance of 5.2 mOhm/m.  The MC4 connectors are specified to have a contact resistance of less than 0.5 mOhm.  While this may be the resistance when tested in a clean and dry factory, test results in warm and humid conditions show a much higher resistance.  Reliability Model Development for Photovoltaic Connector Lifetime Prediction Capabilities indicate resistance in the field is likely to be around 2.5 mOhm.

For the string array, the panels are arranged in the portrait configuration, with the inverter situated 1m from the array.  The panels are about 1.06 m wide, making the length of the array 8.5 m.  Each panel has a 20cm and a 40cm negative and positive output cable.  Unlike the 12 AWG wire used for the PV panel output cables, in Canada, field wiring for PV strings is almost always done with 10 AWG RPVU wire.  This has a resistance of 3.28 mOhm/m, and a total of 10.5 m are used for the array.

With 8 panels, there are 7 connections between panels, plus two connections at the ends mating with the RPVU wire.  The DC connections on the inverter are usually not MC4, but for simplicity the resistance is assumed to be the same.  Adding the positive and negative connections connections to the inverter, the total comes to 11.  Here's the calculations for the total resistance:

10.5 m * 3.28 mOhm/m = 34.4 mOhm
12 AWG 0.6 m panel cables * 8 = 4.8m, * 5.2 = 25 mOhm
11 contacts/string * 2.5 mOhm = 27.5 mOhm
total: 86.9 mOhm

For the microinverter array, the optional 1.4 m PV panel output cables will be needed in order for the cables to reach the corresponding microinverter.  This increased the total length of 12 AWG wire to 22.4 m.  Here's the calculations for the total resistance:

12 AWG 2.8 m panel cables * 8 = 22.4 m, * 5.2 = 116 mOhm
16 contacts * 2.5 mOhm = 40 mOhm
total: 156 mOhm

Although the microinverter configuration higher resistance losses, they are not significant.  During peak power output, DC current is about 10 Amps.  Using P = I^2 * R, power losses are around 0.5%.  Most of the time the array output current is much less than 10 Amps, so the average power loss is much lower.  There are additional losses from the AC bus connectors, which are also not significant.

In conclusion, power losses are higher with microinverters than string inverters, but they are not significant.  The justification for choosing string inverters lies more with the cost savings in material and labor.  For an array with 16 panels, the cost of a 6 kW inverter with 2 string inputs is less than half the cost of 16 Enphase IQ7A microinverters.


Friday, April 16, 2021

Pi ethernet gadget with reverse SSH proxy

 

I love my Pi Zeros.  I think every hacker should have one in their toolbox.  When I got my firs Pi Zero several years ago, I used a USB-TTL serial adapter to connect to the console UART on pins 8 and 10 of the Pi header.  Once I learned how to setup the Zero as an ethernet gadget, things were a bit easier.  However updating software was still a cumbersome process of downloading files to the host computer and then using scp to transfer them to the Pi.  This blog post documents how to setup the Pi to use a SSH reverse proxy so utilities like git and apt work.

When I got my first Pi Zero, I chose the Pi OS Lite image.  I decided to update to the March 4, 2021 release, and this time I used the Pi OS with desktop because it includes development tools like git.  I followed the ethernet gadget setup instructions, modifying config.txt, cmdline.txt, and creating an empty file called "ssh".  The next step is to configure the multicast DNS component of Zeroconf.  As mentioned in the Adafruit instructions, if you are using Windows, the easiest way to do this is installing Apple's Bonjour service.

To use a reverse proxy over ssh, Windows users can't use putty as that feature is not supported.  OpenSSH supports reverse socks5 proxies as of version 7.6.  For connecting from Windows, I installed MSYS2, including OpenSSH 8.4.  On Windows 10, WSL is probably the easiest option.  To connect to the Pi and enable a reverse socks5 proxy on port 1080, enter, "ssh -R 1080 pi@raspberrypi.local".

Once connected to the Pi, set "http_proxy" to "socks5h://localhost:1080".  The "h" at the end is important as it means the client will do hostname (DNS) resolution through the proxy.  I added the following line to .profile to set it every time I login:

export http_proxy="socks5h://localhost:1080"

Programs such as git and curl will automatically use the socks proxy when the http_proxy environment variable is set.  Note that github defaults to showing https URLs for repositories, which need to be changed to "http://" for the proxy to work.

The last configuration I recommend is setting the current date, since the Pi does not have a battery-backed RTC.  I normally use ntpdate from the ntp project for manually setting the date and time on Linux, but it does not work with a socks proxy.  After some searching I found a suggestion of using the HTTP Date: field from a reliable internet server.  The command I use is:

date -s "`curl -sI google.com | grep "^Date:" | cut -d' ' -f3-7`"

Once the Pi Zero is configured and has the proper date and time set, I recommend running "apt update".  If everything is working properly, it will use the socks5 reverse proxy to connect to the raspbian servers and update the local apt repository cache.


Saturday, April 3, 2021

Honey, I shrunk the Arduino core!

 


One of my gripes about the Arduino AVR core is that it is not an example of efficient embedded programming.  One of the foundations of C++ (PDF) is zero-overhead abstractions, yet the Arduino core has a very significant overhead.  The Arduino basic blink example compiles to almost 1kB, with most of that space taken up by code that is never used.  Rewriting the AVR core is a task I'm not ready to tackle, but after writing picoCore, I realized I could use many of the same optimization techniques in an Arduino library.  The result is ArduinoShrink, a library that can dramatically reduce the compiled size of Arduino projects.  In this post I'll explain some of the techniques I used to achieve the coding trifecta of faster, better, and smaller.

The Arduino core is actually a static library that is linked with the project code.  As Eli explains in this post on static linking, libraries like libc usually have only one function per .o in order to avoid linking in unnecessary code.  The Arduino doesn't use that kind of modular approach, however by making use of gcc's "-ffunction-sections" option, it does mitigate the amount of code bloat due to the non-modular approach.

With ArduinoShrink, I wrote more modular, self-contained code.  For example, the Arduino delay() function calls micros(), which relies on the 32-bit timer0 interrupt overflow counter.  I simplified the delay function so that it only needs the 8-bit timer value.  If the user code never calls micros() or millis(), the timer0 ISR code never gets linked in.  By using a more efficient algorithm and writing the code in AVR assembler, I reduced the size of the delay function to 12 instructions taking 24 bytes of flash.

In order to minimize code size and maximize speed, almost half of the code is in AVR assembler.  Despite improvements in compiler optimization techniques over the past decades, on architectures like the AVR I can almost always write better assembler code than what the compiler generates.  That's especially true for interrupt service routines, such as the timer0 interrupt used to maintain the counters for millis() and micros().  My assembler version of the interrupt uses only 56 bytes of flash, and is faster than the Arduino ISR written in C.

One part that is still written in C is the digitalWrite() function.  The Arduino core uses a set of tables in flash to map a given pin number to an IO port and bit, making for a lot of code to have digitalWrite(13, LOW) clear PORTB5.  Making use of Bill's discovery that these flash memory table lookups can be resolved at compile time, digitalWrite(13, LOW) compiles to a single instruction: "cbi PORTB, 5".

ArduinoShrink is also designed to significantly reduce interrupt latency.  The original timer0 interrupt takes around 5us to run, during which time any other interrupts are delayed.  The first instruction in my ISR is 'sei', which allows other interrupts to run, reducing the latency impact to a few cycles more than the hardware minimum.  The official Arduino core disables interrupts in several places, such as when reading the millis counter.  My solution is to detect if the millis counter has been updated and re-read it, thereby avoiding any interrupt latency impact.

The only limitation compared to the official AVR core is that the compiler must be able to resolve the pin number for the digital IO functions at compile time.  Although the pin may hard-coded, even with LTO enabled, avr-gcc is not always able to recognize the pin is a compile-time constant.  Since AVR is not a priority target for GCC optimizations, I can't rely on compiler improvements to resolve this limitation.  Therefore I plan to write a version of digitalWrite that is much smaller and faster, even when avr-gcc can't figure out the pin at compile time.

Although ArduinoShrink should be compatible with any Arduino sketch, given some of the compiler tricks I've used it's not unlikely I've missed a potential error.  If you do find what you think is a bug, open an issue in the github repository.


Tuesday, March 2, 2021

Writing USB firmware on the CH55x MCUs

Over the last several months, I've been familiarizing myself with the CH552 and CH551 MCUs.  Most recently, I've been learning how to program the USB serial interface engine on these devices.  The USB interface is powerful and flexible enough to implement many different kinds of USB devices, from HID to CDC serial.  The highlights are:

  • support for endpoints 0 through 4, both IN and OUT
  • 64-byte maximum packet size
  • DMA to/from xram only
  • multiple USB interrupt triggers
One of the first requirements for writing USB firmware is writing the descriptors.  The examples from WCH are difficult to use as a template due to the descriptors being uint8_t arrays instead of structures.  There are USB structure and constant definitions in ch554_usb.h, which I recommend using instead of arrays.  For instance, I changed the CDC serial example from :

__code uint8_t DevDesc[] = {0x12,0x01,0x10,0x01,0x02,0x00,0x00,DEFAULT_ENDP0_SIZE,
0x86,0x1a,0x22,0x57,0x00,0x01,0x01,0x02,
0x03,0x01
};

to:
__code USB_DEV_DESCR DevDesc = {
.bLength = 18,
.bDescriptorType = USB_DESCR_TYP_DEVICE,
.bcdUSBH = 0x01, .bcdUSBL = 0x10,
.bDeviceClass = USB_DEV_CLASS_COMMUNIC,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = DEFAULT_ENDP0_SIZE,
.idVendorH = 0x1a, .idVendorL = 0x86,
.idProductH = 0x57, .idProductL = 0x22,
.bcdDeviceH = 0x01, .bcdDeviceL = 0x00,
.iManufacturer = 1, // string descriptors
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1
};

Once the descriptors are written, the code to handle device enumeration is mostly boilerplate and can be copied from one of the examples.  During the firmware development stage, I recommend adding a call to disconnectUSB() near the start of main().  It's a function I added to debug.h which forces the host to re-enumerate the device.  This way I don't have to unplug and re-connect the USB module after flashing new firmware.

Setting up the DMA buffer pointers requires special attention when multiple IN and OUT endpoints are used.  Even though five endpoints are supported, there are only four DMA buffer pointer registers: UEP[0-3]_DMA.  When the bits bUEP4_RX_EN and bUEP4_TX_EN are set in the UEP4_1_MOD SFR, the EP4 OUT buffer is UEP0_DMA + 64, and the EP4 IN buffer is UEP0_DMA + 128.  Endpoints 1-3 have even more complex buffer configurations, with optional double-buffering for IN and OUT using 256 bytes for four buffers starting from the UEPn_DMA pointer.

When I first started writing USB firmware for the CH551 and CH552, I was concerned that it may be difficult to meet the tight timing requirements, particularly for control and bulk packets that can have multiple in a single 1ms frame.  For example, with small data packets, the time between the end of one OUT transfer and the end of the next OUT transfer can be less than 20uS.  If the USB interrupt handler is too slow, the 2nd OUT transfer could overwrite the DMA buffer before processing of the first has completed.  This situation is avoided by setting bUC_INT_BUSY in the USB_CTRL SFR.  When this bit is set, the SIE will NAK any packets while the UIF_TRANSFER flag is set.  Therefore I recommend setting bUC_INT_BUSY, and clear UIF_TRANSFER at the end of the interrupt handler.

I am currently working on the CMSIS_DAP example.  It implements the DAPv1 (HID) protocol supporting SWD transfers, and works well with OpenOCD and pyOCD.  I'm working on adding CDC/ACM for serial UART communication.  The first step is creating the descriptors for the composite CDC + HID device.  The second step will be integrating the usb_device_cdc code.  The final step, although not absolutely necessary, will be optimizing the CDC code for baud rates up to 1mbps.  The current code uses transmit and receive ring buffers with data copied to and from the IN and OUT DMA buffers.  With double-buffering, the transmit and receive ring buffers can be omitted.  The UART interrupt will copy directly between SBUF and the appropriate USB DMA buffer.