Xantrex C40 CM meter interface to Arduino controller

slcasner

Joined Aug 11, 2013
13
I like your experimental approach. I have not attempted to do any probing inside the C40 itself because mine are installed as part of my home solar PV system that is operating every day. I've only decoded the the data stream going to the meter and reverse engineered the circuit in the C40 DVM meter itself to see how it used the data stream to drive the LCD meter. I don't have the firmware for the PIC MCU inside the C40 DVM. I, too, used the technique of sending the data bytes via serial line to a computer where I could analyze and graph the data.

By "first byte" I do mean the 8 bits that follow the bit time where both data lines are high. What I observed is that the value decoded from those bits matches exactly with the voltage value displayed on the C40 DVM when it is divided by 12.867 and rounded to one decimal place to get the real voltage as displayed for a 12V system, or subsequently multiplied by 2 or 4 for a 24V or 48V system. Inverting that value gives a slope of 0.0777, which is close to the 0.0755 that you observed.

In my system, the value of byte 13, which you identified as the battery voltage, varied between +1 and -11 with a mean of about -3 relative to byte 1 during a 9-day recording of the data taken in November. The offset is not constant and the values of byte 1 and byte 13 don't necessarily change at the same time. That is why I am confident that byte 1 is what drives the voltage display on the C40 DVM, at least as configured in my system. I also observed the data output as a serial byte stream with each 23-byte frame on one line while simultaneously observing the LCD display, and I could see that the LCD display changed when byte 1 changed but before the bits of byte 13 reached the meter. Perhaps byte 1 is the temperature-compensated battery voltage, and since you don't have a battery temperature sensor, you don't see meaningful values there. Alternatively, I know that the C40 can be used in several different configurations, so perhaps our configurations are different.

The resolution of byte 6 did not seem lower in the data I recorded, but in the operational system the battery voltages do not vary much. The binary value of byte 6 is about 10 less than byte 13 on average. The value of byte 5, the PV voltage, has the same mean value as byte 6 during the time when the C40 is ON, but close to zero when the C40 is OFF. Here is a graph
showing these voltage byte values over the 9-day recording (v is byte 1; the one unusual g13 value may a bit error):

voltages.png

It is helpful to have confirmation that byte 12 reflects the temperature inside the C40 unit itself. It appears that the temperature causes the frequency of the oscillator controlling the PIC MCU in the C40 to vary, as is expected with a crystal. As I mentioned in post #18, I observed that the time interval between frames varies with an unusual pattern that is different when the C40 is ON or OFF. I also observed that the minimum interval varies proportionately with the byte 12 temperature value as shown in this graph:
time-times.png

The red and blue lines are the amps produced through my two C40s, each covering a portion of the solar panels. You can see the diurnal pattern. The first three days have weather with clouds and cloud bounce peaks. In the later days, I think the points that drop below the curve are when an airplane flies over. The cyan and yellow lines are the voltages in this 48V system. The magenta and green lines indicate the time interval between frames in an arbitrary time scale measure by a timer in my PIC MCU circuit. Note that one C40 runs faster than the other. The areas that look like solid parallelograms are really many data points with a lot of structure that occurs at night. During the daytime the time varies less. However, notice the brown line which represents the byte 12 (transistor temperature) value scaled and offset to fit it to the magenta curve. The correlation is very close, which would be explained by the temperature of the oscillator crystal varying.
 

Maxzillian

Joined Apr 29, 2015
8
Yeah, trying to do it on a real system can be a challenge. I reconfigured mine to display the first byte and it alternates between 205, 155 and 77 with the battery voltage at 12.1. Binary wise these numbers show as:

205: 11001101
155: 10011011
77: 01001101

As it turns out, there was an error in my code. I was using the sync signal to start a new array of data, but failed to do anything about the byte in progress... oops. That was a mistake I wrote into it pretty early on so it's no wonder I dismissed byte 1. After correcting that I get good steady readings within a few digits of byte 13, but it does occasional dip down to half the value. Somehow I'm getting an extra bit which is making me catch something like 01001101 instead of 10011010; I've ran out of time tonight to figure out what's up.

I think this has to do with not receiving the sync signal everytime; have you found occasions where both comm lines don't go high? Either that is really happening on my system or I'm just not capturing it with the Arduino.

What I mean by byte 6 being a low resolution is that it reads voltage on a larger scale; the same scale as the input voltage. In my findings, 6 is a scale of 0-89 volts while 1 and 13 appear to go up to 19 volts in 12 volt mode.

I went ahead and threw a 100k thermistor on the battery voltage connection and found that byte 13 does vary with battery temperature, so byte 1 must be the real voltage while 13 is the comparator voltage used for the charging logic. Warming up the thermistor makes the byte-13 voltage reading go higher which means the battery should stop charging at a lower voltage. This would be the desired behavior as the batteries warm up.

My oscilloscope, being old and decrepit, does a poor job of capturing the communication so I haven't noticed the frame duration changing. However, I haven't been looking for it either. Do you think this is just an oddity of the system or is there a design reason for this? Not having a crystal to stabilize the MCU clock would certainly explain it.
 

Maxzillian

Joined Apr 29, 2015
8
I think I know why I'm missing the sync; I'm writing the two pin inputs to a variable and making a comparison with the pair every loop; but that assumes that the MCU picks up that both pins are high at exactly (or nearly) the same time when that is not likely the case. I'll have to write in a small delay and have the MCU check the pins again to capture the pair more reliably.
 

slcasner

Joined Aug 11, 2013
13
The reason I looked at the interval between frames was to detect whether I was missing any. The receiving circuit in the C40 DVM includes a capacitor that causes a rather slow rise time on the comm lines, and that has caused some difficulty with getting reliable sampling. However, I have not had any more difficulty sampling the frame sync bit than any of the other bits. I doubt that all the variation in the interval is by design. Instead I suspect that the MCU is busy doing other things, so the timing varies. I was assuming that the C40 had a crystal oscillator since the C40 DV, but I see now in the schematic that Auda posted that the C40 MCU is using its internal oscillator. That would make it more susceptible to temperature variation, although even crystal oscillators will vary with temperature (order of 10^-5).

Regarding sampling, what I do is to use the edge-detection interrupt triggered by either comm line to start a timer for half a bit time, then sample. Actually, my most recent change was to delay to later in the bit time because of the slow rise time caused by the capacitor.
 

Maxzillian

Joined Apr 29, 2015
8
I finally got around to correcting the code, when either pin goes high it will delay a millisecond and check the other pin a second time. This seems to have resolved the problem with it missing that both pins were high due to the small delay that occurs between both going high. I've also corrected my notes to reflect byte 1 is in fact battery voltage as well as corrected the code to display that on the LCD.

I've also still got to make a schematic as well as investigate byte 4 which I suspect may be a milliamp-Hour counter; displaying how many milliamp-hours were collected since the last frame of data.
 

Maxzillian

Joined Apr 29, 2015
8
Byte 4: There's a chance it could be a milliamp-hour counter. I performed a handful of tests and found the results to be reasonably close:

2 amp charge rate:
Test 1- Byte 4- 972 mAH
Test 1- Calced- 938 mAH
Test 2- Byte 4- 993 mAH
Test 2- Calced- 960 mAH
3.5% error

10 amp charge rate:
Test 1- Byte 4- 991 mAH
Test 1- Calced- 924 mAH
Test 2- Byte 4- 922 mAH
Test 2- Calced- 850 mAH
7.9% error

17 amp charge rate:
Test 1- Byte 4- 992 mAH
Test 1- Calced- 953 mAH
4.1 % error

This is using the formula: (126-x)/4

So reasonably close, but my only concern is that my raw value at 0 amp charge is 126 while with your results you saw 132.
 

slcasner

Joined Aug 11, 2013
13
That 132 value for byte 4 is probably wrong. At that point I was decoding data recorded as an audio stream. After implementing my circuit to capture the data and send as a serial byte stream, I have a recording 18623 frames long that has a minimum value of 86 for byte 4 and curves up to 126 and becomes flat. So that was probably a recording of the last four hours or so of daylight plus an hour of darkness.

I wonder if byte 4 is what the C40 DVM accumulates for its Ah display value.
 

Maxzillian

Joined Apr 29, 2015
8
Maybe, but even on byte 3 you had a value of 132 while mine was 126. Maybe byte 3 is is used to define the value to subtract from for byte 4, but regardless I don't understand why they wouldn't have just configured byte 4 to have a value of 0 at 0 mAH. This whole method of inversion seems a bit asinine.

That said, it would make sense that the charger would calculate the AH generated and send that to the DVM for it to accumulate. Otherwise the DVM has to keep track of time between readings and use the relatively inaccurate amp readings produced by the charger.

What I should probably look into is buying a current clamp or devising some sort of meter to manually count AH and compare it to the reading on the DVM.
 

slcasner

Joined Aug 11, 2013
13
The 132 value for byte 3 was also probably wrong. The longer recording starts out with the value 125 and changes to 126 about 2/3 of the way through the time after byte 4 reached 126. As I was categorizing the bytes as voltages, currents or something else, I put byte 4 in the current category. It is hard to tell the difference between amps and an increment of amp-hours if the time increment is constant.

Since the C40 DVM has a crystal and the C40 does not, it might make more sense for the DVM to keep track of time. It is clear that the accumulation must happen in the DVM since the data stream does not contain a multi-byte value, and since the accumulation resets to zero if the DVM is disconnected (so it loses power). I can report that the implementation of the Ah accumulation in the DVM is a bit flaky. It is a 5-digit decimal number backed by a 16-bit counter so it cycles at 65536. There is also a single-digit fraction, but the units digit does not increment at the same time as the tenths digit cycles to zero, but many seconds later.

I need to go back and look at my data when I was recording both the data byte stream and the LCD display to see how closely an accumulation of the amp values matches the Ah accumulation displayed on the LCD. The problem with that experiment is that I don't have timestamps on the data stream, so I'd have to apply timing from my more experiments as shown in my post #21 above.
 

Maxzillian

Joined Apr 29, 2015
8
The list has everything except the amp-hour counter which I feel hasn't been confirmed yet. I haven't done much testing this last week.
 

Bob Le Snob

Joined Dec 6, 2019
1
So anyone can make me a wrap up of this project?
I am looking to make a Datalogger with a Raspberry Zero and so far from what I understant the correct information are:
"
battVoltage = (((unsigned long)stats[0]*100000)/1325+464)/10
amperage = stats[1] / 2; //calculate amperage, will only get a whole number result
pvVoltage = ((unsigned long)stats[4]*10000)/2857; //do conversion math, result is double point precision (IE, 12.54 volts is 1254)
battTemp = stats[6]; //conversion formula not determined yet
bulkVoltage = (((unsigned long)stats[9]*100000)/1325+464)/10;
floatVoltage = (((unsigned long)stats[10]*100000)/1325+464)/10;"
 

djsfantasi

Joined Apr 11, 2010
9,163
So anyone can make me a wrap up of this project?
I am looking to make a Datalogger with a Raspberry Zero and so far from what I understant the correct information are:
"
battVoltage = (((unsigned long)stats[0]*100000)/1325+464)/10
amperage = stats[1] / 2; //calculate amperage, will only get a whole number result
pvVoltage = ((unsigned long)stats[4]*10000)/2857; //do conversion math, result is double point precision (IE, 12.54 volts is 1254)
battTemp = stats[6]; //conversion formula not determined yet
bulkVoltage = (((unsigned long)stats[9]*100000)/1325+464)/10;
floatVoltage = (((unsigned long)stats[10]*100000)/1325+464)/10;"
Welcome to AAC! Responding to old posts is called necroposting. This behavior is not good forum etiquette. You should start a new thread with your questions. You can refer to this post if you like.

I just scanned through two pages to find out this was a necropost. Please start your own thread.
 
Top