SAMD21 ADC and emonLibCM

Hi All. Thank you to all those involved in contributing to this important open source application.

I am using a Feather M0 under the arduino ide 1.x and have successfully modified the EmonLib code to work with the SAMD21 ADC. To do so I also modified the adaino SAMD ADC library to operate in a way such that the ADC readings are captured and stuffed into multiple ring buffers (using interrupts), one for V and one for each current channel.

This is ok for starters but I would like to implement continuous monitoring and I have been struggling to get anywhere with modifying the emonLibCM library to work with the SAMD21 ADC.

Please note this is not a criticism of the emonLibCM software in any way!

The modified adaino library results in arrays of raw ADC values representing voltage and current readings and provides functions to peek an read these values (in the foreground) and detect buffer or device overrun conditions.

Theoretically it should be relatively easy to modify emonLibCM such that it obtains its readings from this simple buffer interface of V and I readings but I am not getting anywhere fast.

Has anyone done this (emonLibCM with SAMD21) or can anyone point me in the right direction for some example source code ?

Obviously I will be glad to share my resulting source here for all to use.

Best regards,
ozpos

Welcome to the OEM forum, Oz.

I don’t know what you’re talking about (and it’s not a criticism of you :wink:), but a very brief search tells me that adiano is another library. My gut feeling is using emonLibCM to read another library is asking for trouble - because emonLibCM is already working directly with the processor. I think you need to look at what emonLibCM does to set up the processor and read the data, then study and find out how adiano achieves, or could achieve, the same thing. Then modify as little emonLibCM as you need to get the data from your SAMD21, using the knowledge you gained from taking adiano apart.

A couple of warnings:

  1. In my very brief reading, I saw a complaint about a bug in the Arduino code. You need to check that out.
  2. Transformer phase errors etc are heavily tied to the speed of the AVR’s ADC. If your ADC is faster, you might be in loads of difficulty getting the phase error correction right.

Alternatively, if that approach doesn’t work for you, write your own CM library around the adiano library. This might be easier.
If you kept an identical API, it would make it easier for anyone else who might want to use it.

@Robert.Wall I think @ozpos meant Arduino not adiano.

@ozpos emonLibCM accesses the ADC directly using the lower level commands provided by Atmel rather than the Arduino abstraction/simplification.

I dont think anyone has got emonLibCM running on the SAMD21 yet. I wonder if the SAMD21 low level ADC commands are similar to the AVR-DB? You may want to look at the changes we’ve made to the original emonLibCM implementation to get it working on the AVR-DB range: Comparing master...avrdb · openenergymonitor/EmonLibCM · GitHub

Hi Thanks for the response.

Thank you, I am aware that Adaino is another library. I am an ex self-employed embedded real time software engineer.

I chose the Adaino lib for access to the ADC because of its speed and simple api. From it I created my own version by adding methods so that I could access any number of channels. I also created my version of EmonLib that uses this new adc library in place of the stock analogRead(inPin) calls and it worked fine but what I really prefer is for a CM solution.

Thanks for the heads up on possible aduino faults. I am not an arduino expert by any stretch of the imagination.

Having got fast access to multiple ADC channels and a working modified EmonLib I tried to write a CM version of EmonLib but I am not so good at understanding the principals, maths and filtering that is required so I turned to the emonLibCM in an attempt to create a new version using this new ADC lib in place of the existing ADC code but it is proving very difficult for me.

Here is a simple working version of a modified Adaino buffered_freerun.ino example to illustrate how the ADC library is used.

#include <Arduino.h>
#include <AdainoMultiChan.h>

void setup()
{
 // Setup serial port and wait for connection
 Serial.begin(115200);
 //while (!Serial) ;
 
 // Print example information to the serial output
 Serial.println("[AdainoMultiChan EXAMPLE]");
 Serial.println("modified buffered_freerun.ino");
 Serial.println("[AdainoMultiChan DATA]");

 // Set the analog channel and input pin to be sampled
 AnalogIn.setAnalogInput(0,A2);
 AnalogIn.setAnalogInput(1,A3);

 // Use the free running acquisition mode if you want to sample the analog 
 // input continously and at the highest sampling rate possible with the 
 // current settings.
 AnalogIn.useFreerunMode();
 //AnalogIn.setScanMode(0,1); //does not work yet
 
 // If you want to sample the analog input at a higher rate, it is recommended 
 // to enable the acquisition buffer as samples can get lost otherwise.
 AnalogIn.enableAcqBuffer(2000); 
 // Set the resolution. Adaino supports resolutions of 8-, 10- and 12-bits.
 AnalogIn.setResolution(12);

 // Start the analog data acquisition
 AnalogIn.begin();
}

// Create a result buffer for 100 samples.
const int BUFFER_SIZE = 100;
short unsigned int vBuffer[BUFFER_SIZE];
short unsigned int iBuffer[BUFFER_SIZE];

// Define a counter variable.
int counter0 = 0, counter1 = 0, counter = 0;
int analogResult;

void loop()
{
 // Read the analog input and add it to the buffer.
 analogResult = AnalogIn.channelReadInput(0);
 counter0 = add_to_buffer(counter0, analogResult,vBuffer);

 analogResult = AnalogIn.channelReadInput(1);
 counter1 = add_to_buffer(counter1,analogResult,iBuffer);

 if (counter1 >= BUFFER_SIZE) {
   transfer_buffer();  // send to serial out.
   counter0 = counter1 = 0;
 }
}

// add_to_buffer() adds a result to the buffer and increments the buffer index (counter) by one.
int add_to_buffer(int counter, int result,short unsigned int * buf) {
 buf[counter] = result;
 return counter + 1;
}

// transfer_buffer() sends the full buffer to the serial output and blocks your
// sketch while active. It is likely that you have similar functions in your 
// sketch if you process the acquired data or communicate with other devices or 
// the worldwide web.
void transfer_buffer() {
 for (int i=0; i < BUFFER_SIZE; i++) {
   Serial.print(vBuffer[i]);
   Serial.print(",");
   Serial.println(iBuffer[i]);
 }
}

Any help would be greatly appreciated.

Oz

Hi @TrystanLea thanks.

Do you think others would find a working version of emonLibCM for the Feather M0 useful ?

Oz

Aha ok, sorry my bad!

It does sound interesting. I know the subject of the SAMD21 has been raised here before and it’s something Id like to look at at some point.

I’m afraid I’m fully committed to the emonTx V4, I simply cannot find the time to take on anything like this at the moment - at least not to the depth it will require.

Put very simply, the electrical engineering bit is you sample voltage and current simultaneously, multiply the samples together to give the instantaneous power, average that over time to give the average power. The problem arises when you use transformers to ensure isolation and safe voltages to handle, because the transformer output leads (mostly) the input by a few degrees, it’s called the phase error, and it varies from device to device, and according to the amplitude of the quantity being measured. That’s why it becomes necessary to create (by interpolation) a synthetic voltage wave that can be shifted in time to exactly line up with the current wave. Not doing that gives erroneous values - not too bad with unity power factor loads, but increasingly bad as the power factor deteriorates.

The way emonLibCM operates is it sets the ADC off free running. Each sample converted by the ADC is dropped into a variable and an interrupt generated. The ADC has already switched the multiplexer to the next channel and started converting the next sample, so the ISR pre-loads the MPX with the next channel to sample after that. So you have a 3 stage process: the ADC is converting a sample, the ISR sets up the next channel to sample and processes the last channel sampled. That’s the core of it.
The ISR accumulates the various quantities and hands them back to the main loop for calibration into engineering units and sends them on their way.

There’s no filtering as such. The maths is a matter of converting the raw samples into engineering units. Voltage and current are rms (root mean square) average values, while power (real power or active power) is a simple average. Apparent power is Vrms × Irms, and power factor is the ratio Real Power ÷ Apparent Power. Energy is the time integral of power.

The basics are explained in the ‘Learn’ section.

Hi Robert, thank you for taking the time to explain the theory. I understand your situation.

I have simply modified the Adaino Lib so that each configured channel is converted in round robin fashion and the results are stored in multiple ring buffers by the ISR (one buffer for each channel). The delay between readings is minimal but deterministic so that any significant phase shift could be corrected on the fly.

Now I know that there may be some interest in a SAMD21 version I will report back any progress that I make.

Many thanks and best regards,
Oz

Another option might be to look at the stm32 work done by @danbates (searchable in this forum). That’s possibly a closer starting point to what your h/w is providing: a big array of V and I readings pre-loaded by ISRs and/or DMA in the background.

Thank you I will take a look

Hi All, Sorry if I have gone against convention but I do not know the correct way of introducing a progress report other than replying to my original post. Please feel free to inform me of the correct way.

I have successfully built emonLibCM v2.2.2 for the SAMD21.

Initial results look promising in that the returned values seem to be quantitatively OK (if I turn on a load it registers power and if I double the load then the registered power doubles) except for f which is reported as 10.88. The actual power as well as pf are off quite a bit

AC present 
 V=234.09 f=10.90
Ch 1 I=0.405 W=-58 VA=95 Wh=-58 pf=-0.6252

Load applied (fan heater).

AC present 
 V=234.76 f=10.89
Ch 1 I=3.687 W=862 VA=866 Wh=-140 pf=0.9958

Load x2 applied.

AC present 
 V=234.10 f=10.88
Ch 1 I=7.377 W=1725 VA=1727 Wh=-9 pf=0.9989

Could anyone please clarify what clock the following hard coded variable refers to or even better what value I should use here in place of 106us for the 48Mhz SAMD21 Feather M0 ?

int ADCDuration = 104;                                                 // Time in microseconds for one ADC conversion = 104 for 16 MHz clock 

Many thanks in advance,

Oz

P.S I suppose the no load pf is expected with a 100A CT and the pf with load is pretty good.

Great, good effort!

How does the SAMD21 sample the analog channels are they all done sequentially or are some done in parallel? ADCDuration is the time for each sample in micro-seconds, what’s your sample rate? there may be quite a few configuration options that define the sample rate and you may need to run slower than maximum due to input impedance and other factors…

That, along with a lot of other data, is in the documentation that comes with the download.

1 Like

Hi @TrystanLea, sorry for the delay I have been away from my PC for a couple of days.

Many ways except simultaneously however any phase delay is deterministic. See section 32. https://www.mouser.com/datasheet/2/744/Atmel_SAMD21_datasheet-2492175.pdf

As you say several settings will modify the sampling rate which raises a question I have for anyone;

Given the features below, which ones should be chosen to make the best quality results without modifying the existing 2.2.2 code and with similar circuitry optimised for 3.3v VDD ?

32.2 Features

  1. 8-, 10- or 12-bit resolution
  2. Up to 350,000 samples per second (350ksps)
  3. Differential and single-ended inputs
  • Up to 32 analog inputs
  • 25 positive and 10 negative, including internal and external
  1. Five internal inputs
  • Bandgap
  • Temperature sensor
  • DAC
  • Scaled core supply
  • Scaled I/O supply
  1. 1/2x to 16x gain
  2. Single, continuous and pin-scan conversion options
  3. Windowing monitor with selectable channel
  4. Conversion range:
  • Vref [1v to VDDANA - 0.6V]

  • ADCx * GAIN [0V to -Vref ]

  1. Built-in internal reference and external reference options
  • Four bits for reference selection
  1. Event-triggered conversion for accurate timing (one event input)
  2. Optional DMA transfer of conversion result
  3. Hardware gain and offset compensation
  4. Averaging and oversampling with decimation to support, up to 16-bit result
  5. Selectable sampling time

For example should the ADC clock be set to a max and 23 be used in preference to 24 ?

Many thanks in advance,
Oz

Could anyone please tell me if I should keep the number of samples/cycle the same as 2.2.2 for the AVR @16MHz (192) and use up all the spare time as extended sampling time or use the spare time doing oversampling/averaging and decimation ?

I am able to work with any combination of settings but I do not have the means intellectually or physically to check their influence over signal to noise ratio and accuracy ?
Oz

Hello @ozpos try setting it to a fairly slow sample rate to start with, similar to the AVR and then increase the speed to find the point at which it becomes unstable. I went through a similar process with the AVR-DB, I need to take another look at that and refresh on why I choose the settings it’s on at the moment.

Hi @TrystanLea, thanks for your response. Experimenting with pre-scaler values an sampling times yields what look like OK results across a wide range but I do not know where I should aim for. I know this is a dumb question but it is not my area of expertise. The ADC is capable of 1.7Ms/s without gain in free-running mode. e.g. Over-sampling/decimation of 256 samples yields 16-bit accuracy @ about 6k7Hz.

Are the instabilities you refer to caused by software overflows with 64bit integer arithmetic ?

Another thing I should mention is that my approach here w.r.t. the software is to ‘#ifdef’ out AVR ADC reference and ‘#ifdef’ in working code for SAMD21 in order to make the code easier to integrate with 2.2.2 and not change the size of any integer results arithmetic in any way,

This is the line that sets the ADC sample time in the latest EmonTx4 firmware: https://github.com/openenergymonitor/emontx4/blob/main/firmware/EmonTxV4CM_rfm69n/EmonTxV4CM_rfm69n.ino#L225
It’s set to 29.5us, this relates to ADC0.SAMPCTRL = 14 and ADC_PRESC_DIV24_gc on the AVR-DB.
That’s about 33.4 kHz.

Here’s an example output with a 6W lamp on the desk and CT’s 1-5 connected:

MSG:44,Vrms:239.85,P1:7,P2:6,P3:6,P4:6,P5:6,P6:0,E1:0,E2:0,E3:0,E4:0,E5:0,E6:0,pulse:0

If I try ADC0.SAMPCTRL = 14, ADC_PRESC_DIV16_gc which should be ~19.6us, things start to go wrong. Vrms is much lower and P6 is now very high even though there is nothing connected.

MSG:5,Vrms:215.40,P1:5,P2:6,P3:5,P4:5,P5:3,P6:454,E1:0,E2:0,E3:0,E4:0,E5:0,E6:6,pulse:0

If I try ADC0.SAMPCTRL = 14, ADC_PRESC_DIV20_gc which should be ~25us, voltage is a bit better but p6 is still reading a little high, though better than before.

MSG:2,Vrms:243.32,P1:9,P2:6,P3:6,P4:6,P5:6,P6:14,E1:0,E2:0,E3:0,E4:0,E5:0,E6:0,pulse:0

Im not sure to be honest. The ISR timings looked ok if I remember.

That’s great