el-cheapo mcu-based LCR meter - Page 1
Forum section: electronics / projects

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 12:18:37 pm
content: I have done some LCR meters built around with mcus (PICs) with some external circuitry. The simplest of them utilized some opamps and algorithm to detect the maginitude and phase shift, thus the L/C/R parameters.With the newer mcus incorporating lots of analog stuff, it is possible to do that with minimum external circuitry.So this is one such attempt.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 12:27:48 pm
content: Operating principle:We know that when we drive an unknown (passive) circuit from a voltage source at a given frequency, the current through and voltage over the unknown circuit are dependent on its L/C/R parameters. The magnitude is dependent on the impedance, and the phase shift on the reactance.So if we measure the voltage, and current - both magnitude and phase shift, we would have detected the L/C/R parameters.The following example exhibits the principle. A 1khz source driving a 1u capacitor. We can see that the current leads the voltage by 90 degrees so we know the DUT is a capacitor.The impedance is approximately 1v/6.2ma=161ohm. At 1k, that translates into 0.987uf, vs. 1uf theoretical.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 12:31:18 pm
content: The circuit above is simple but difficult to be implemented around a mcu, due to its being bipolar.That's easy to solve, as we just need to pad the ground up.Here, we padded the signal to 1/2 of Vcc (=3.3v).The AC voltage / current amplitude and phase shift unchanged.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 12:48:11 pm
content: How do we operationalize that?We know that we will need padding to make the operation unipolar. We also know that we will need to measure both current and voltage.The DUT may be of very low impedance so we will need some current limiting, while it being driven by our voltage source / excitation signal.So here is what we have.DAC1 generates our excitation signal. We may need to change the amplitude + frequency; U1 is its buffer.DAC2 generates the padding, U2 is its buffer.C1 is our DUT, current limited by R2 - chosen so that it doesn't exceed the operating parameters of U1/U2.U3 buffers / amplifies the voltage signal.U4 is our I-to-V converter.That's basically there is, in theory anyway.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 12:51:24 pm
content: The actual implementation would vary, however, based on the mcu's used.For example, if we picked a chip with two DACs (with internal bufers), two ADCs, and two opamps, we are done! The only external parts would be those two resistors + DUT! Truly a minimalist approach.Worse case scenario would be to use four opamps (two dual opamps) and go from there.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 12:59:09 pm
content: What kind of mcus do we need?Well, it needs to be reasonably fast.It needs to generate two analog voltage signals;It needs to be able to perform ADC - preferably with two separate adc modules.We would like the generation of the excitation signal to be entirely in the background. So some DMA capabilities would be a plus.As this is an el cheapo project, we want it to be as inexpensive as possible. So some kind of existing pre-made modules would be great.For that, I would start with the Nucleo-L152 board, featuring a STM32L152 chip: 32Mhz (not ideal but should be enough), 512Kb flash + 80Kb ram (more than sufficient), 1 12-bit adc (less than ideal but doable), 2 dac (with buffers = perfect), and 2 opamps (perfect), 12 ch DMA (more than sufficient).All for less than $20 -> perfect.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 01:00:05 pm
content: Note:once we get it going on the L152, we will see how promising it is and we will migrate it to other mcus, like my favorite: STM32F030F.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 01:03:11 pm
content: First order of business is to generate the excitation signal + padding.We can do it via DAC (driven by a sine table), or filtering a square wave from the PWM generator, or via SPWM.The DAC approach requires more hardware, but is the simplest, and supports multiple frequency output.The square wave approach is the least expensive from a hardware point of view, but requires filtering so it can only be done at a fixed frequency.The SPWM is the most expensive approach from a software perspective -> you may be able to link it via DMA + PWM generator. It is limited on the upper end.We will use the DAC approach.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 09:27:15 pm
content: Worked on it a little bit and got the signal generation portion of the code working.Here is a STM32L152, running at 32Mhz and generating 100Hz, and 1KHz signal, both buffered.

author: dannyf
author_rank: Super Contributor
timestamp: December 29, 2014, 09:28:32 pm
content: The top end is limited just above 5Khz.I can fix that by shortening the data points in my sine table (now at 200 points), to 100 points for example. Not sure if it is worth it but will try later.

author: dannyf
author_rank: Super Contributor
timestamp: December 30, 2014, 02:37:44 am
content: I have incorporated dsp_lib (1.0.11) into the project. With real fft in, code size has grown to over 14KB flash and about 7Kb ram. A rebuild is ~90 seconds.I may be able to trim some fat but the chance of shrinking it down to a 16KB device (STM32F030F) is not looking good.

author: briselec
author_rank: Regular Contributor
timestamp: December 31, 2014, 02:08:01 am
content: So if I was to build a LCR/ESR meter would the Discovery Kit with STM32F429ZIT6U MCU be a good candidate?

author: dannyf
author_rank: Super Contributor
timestamp: December 31, 2014, 12:49:30 pm
content: QuoteIt is a 180Mhz mcu, with fpu, 3 adc, 2 dac, dma, far more than enough to run this, potentially at 100Khz. It also has a lcd module on board tooOn the flip side, you may want to look at the pins utilized by the mcu and see if it takes any of the dac / adc pins (a must). If it does, you may have to use a different display device.

author: dannyf
author_rank: Super Contributor
timestamp: January 01, 2015, 05:10:04 pm
content: Worked on the signal processing part of this a little bit today.Basic question: from series of measurements for volt and current, how does one calculate 1) amplitude and 2) (relative) phase shift?The typical analog approach is synchronous detection, using an orthogonal signal.In the digital domain, the most universal and surest way would be FFT/DFT (or DCT). It not only detects amplitude, phase shift, but also any harmonics, as it makes no assumption about the excitation signal as well as the circuit to be subject to the excitation signal.Its downside is primarily speed and size: real FFT takes over 10 million instructions to complete (200 points, unoptimized and no fpu used) and has a foot print, with all other support functions, 10KB+ in flash and 4-5Kb in ram.On the other extreme, you have IEEE1057, the two- or three-parameter implementation can be done very quickly and offers reasonably good precision, particularly when you have a lot of data.

author: dannyf
author_rank: Super Contributor
timestamp: January 01, 2015, 05:20:16 pm
content: I am going to take a different approach, which as you can see is a direct replication of the analog synchronous detector in the digital domain.FFT is time consuming because it does not fully incorporate all the information here. We know the excitation signal (it is a sine wave of a known frequency), and we know the underline circuit to be tested (it is a linear system).Our approach takes advantage of that knowledge.Assume that we have two series of data,voltage measurements: V=V*sine(wt), where V is the peak value. Initial offset doesn't matter thus omitted. V is unknown.current measurements: C=C*sine(wt + alpha), where alpha is the (relative) phase shift and is unknown. C is unknown.If you integrate V*Vover one full cycle, take the average of that, you get the rms of V: V(rms) = sqrt(Average(V*V)).Similiarly, C(rms) = sqrt(Average(C*C))Once you have the rms values, you have the peak value V and C, respectively.If you were to integrate Vand Cover one cycle, you get 1/2*V*C*cos(alpha).So the phase shift alpha = acos(sum(V*C) / (0.5*V*C)) = acos(sum(V*C) / (V(rms) * C(rms)).I have tested this approach using gcc with good results. Will post the code later.

author: dannyf
author_rank: Super Contributor
timestamp: January 01, 2015, 05:21:30 pm
content: BTW, there are other ways to figure out the phase shift. For example, you can approaximate zero-crossing and calculate the relative phase difference between the voltage and current's zero-crossing points.

author: dannyf
author_rank: Super Contributor
timestamp: January 01, 2015, 05:22:30 pm
content: Another approach is to multiple the V/C vectors by orthogonal signals to obtain the real and imaginary parts.I will try them later when I get some time.

author: briselec
author_rank: Regular Contributor
timestamp: January 01, 2015, 10:55:39 pm
content: Going from their datasheet, with the STM32F429I-DISCO you lose one DAC output channel to LCD_VSYNC, one input channel common to the 3 ADCs and 3 input channels common to ADC 1 and 2.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 12:57:37 pm
content: You would need a minimum of 1 DAC output - none if you don't need to test under multiple frequencies;You would also need a minimum of 1 ADC module, two ADC channels - easier to handle with two ADC channels on two ADC modules.Sounds like it would work. The higher clock rate, presence of a fpu and a lcd display is a plus.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 01:04:55 pm
content: Here is the code I used to test the digital synchronous detector.Code:#include <stdio.h>#include <stdlib.h>#include <math.h>#include <time.h>//we use time to randomize the data#include "fft_fp.h"//floating point fft//global defines#ifndef PI#define PI3.1415926#endif#define PIx2(2*PI)#define DFT_SIZE256#define VOLT_FS1024//adc swing, full scale#define VOLT_offset2048#define AMP_GAIN1000.0//gain on current amplifier#define PHASE_INIT1//initial phase offset - doesn't matter//global variablesfloat volt_f, amp_f;//compx dat;//data//generate a block of data//input is a sine wave at freq_hzvoid sigen_rc(float *volt,//output voltagefloat *amp,//output currentfloat res_ohm,//resistive load, in ohmfloat cap_uf,//capacitance, in uffloat freq_hz,//test signal frequency, in hzfloat noise_volt,//noise for voltage,float noise_amp,//noise for currentint block_size//length of data to be generated) {int index;float impedance_res=res_ohm;//real resistancefloat impedance_cap=1/(PIx2*freq_hz*cap_uf*1e-6);//imaginary impedancefloat impedance=sqrt(impedance_res * impedance_res + impedance_cap * impedance_cap);float alpha;//phase shift relativesrand(time(NULL));//seed the random number generatorprintf("freq = %10.4fHz\n\r", freq_hz);printf("impedance_res = %10.4f, impedance_cap = %10.4f, impedance_total = %10.4f\n\r", impedance_res, impedance_cap, impedance);alpha=asin(impedance_cap / impedance);//phase shiftprintf(" input: impedance = %10.4f, phase shift = %10.4f degrees\n\r", impedance, alpha * 360 / (PIx2));freq_hz=1;//we sample in one cycle onlyfor (index=0; index<block_size; index++) {volt=VOLT_FS*sin(PIx2*freq_hz*index/block_size + PHASE_INIT) + noise_volt * (rand()/32768.-0.5);volt+=VOLT_offset;//plus offsetamp=VOLT_FS/impedance*AMP_GAIN*sin(PIx2*freq_hz*index/block_size - alpha + PHASE_INIT) + noise_amp * (rand()/32768.0-0.5);//current leading voltage in a rc circuitamp+=VOLT_offset;//give it some offset}}//void de_DC a series//not usedvoid dsyncdedc_f(float *dat, int block_size) {int i;float avg=0;for (i=0; i<block_size; i++) avg += dat;//calculate the sumavg = avg / block_size;//calculate the averagefor (i=0; i<block_size; i++) dat -= avg;//de-dc the sample}//calculate impedance + phase shift from v/i datavoid dsync_f(float *volt, float * amp, float *imp, float *dph, int block_size) {int i;float va=0;//volt * ampfloat v_rms=0;//v_rmsfloat a_rms=0;//a_rmsfloat v, a;//de-DC the data seriesdsyncdedc_f(volt, block_size);dsyncdedc_f(amp, block_size);/*//de-DC the data series - for speedv_rms=a_rms=0;//use those variables to de-DC the volt / amp seriesfor (i=0; i<block_size; i++) {v_rms+=volt;//sum up the seriesa_rms+=amp;//sum up the series}v_rms /= block_size;//calculate the averagea_rms /= block_size;for (i=0; i<block_size; i++) {volt-=v_rms;//de-DCamp -=a_rms;}v_rms = a_rms = 0;//reset v_rms/a_rms*///for (i=0; i<block_size; i++) printf("i=%10d, volt=%10.4f, curt=%10.4f\n\r", i, volt, amp);//return;for (i=0; i<block_size; i++) {v=volt;//to save timea=(float)amp / AMP_GAIN;//nomalize amp by its gain factorva+=v*a;//va productv_rms+=v*v;//v*va_rms+=1.0*a*a;//a*a//printf("i=%10d, v=%10.4f, a=%10.4f, va=%10.4f, v_rms=%10.4f, a_rms=%10.4f\n\r", i, v, a, va, v_rms, a_rms);};//printf("sum:va=%10.4f, v_rms=%10.4f, a_rms=%10.4f\n\r", va, v_rms, a_rms);va /= block_size;//calculate the averagesv_rms = sqrt(v_rms / block_size);a_rms = sqrt(a_rms / block_size);//printf("avg:va=%10.4f, v_rms=%10.4f, a_rms=%10.4f\n\r", va, v_rms, a_rms);*imp=v_rms / a_rms;//impedance*dph=acos(va / (v_rms * a_rms));//calculate phase shift//printf("va=%10.4f, v_rms=%10.4f, a_rms=%10.4f\n\r", va, v_rms, a_rms);printf("impedance=%10.4f, dphase=%10.4f\n\r", *imp, *dph * 360/2/PI);}float impedance, dphase;int main() {//float imp, dphprintf("Hello world!\n");//using floatprintf("using floating point math.\n\r");sigen_rc(volt_f, amp_f,1,//resistance, in ohm1,//capacitance, in uf100,//signal frequency, in hz10,//voltage noise10,//current noiseDFT_SIZE);//data length//fft_fp(dat, FFT_FORWARD);dsync_f(volt_f, amp_f, &impedance, &dphase, DFT_SIZE);printf("output: impedance = %10.4f, phase shift = %10.4f degrees\n\r", impedance, dphase*360.0/2/PI);return 0;}Some notes:1) dsync_f() is the floating math version of the routine. It takes two arrays of data, and calcualtes impedance and (relative) phase shift. To save on space, an alternative is to calcualte the real and imaginary parts of the impedance.2) sigen_rc() generates a series of simulated measurements on a rc circuit. The parameters of the circuit are specified as input to the routine.3) sigen_rc() also simulates the impact of a noise to both measurements. The user can control those parameters to see what impact, if any, there are, on the final estimates.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 01:11:17 pm
content: When you run the code (tested on both turbo C and gcc), it spits out the frequency, and impedance parameters it runs under.The "input:..." line tells you the actual circuit parameters: in this case, it is a 1uf capacitor (1591.5494ohm impedance @ 100Hz), with a 1ohm ESR, for a combined total impdance of 1591.5498ohm, and an angle of 89.9611 degrees.The estimated figures are shown on the "output:..." line: 1591.7283ohm, and an angle of 89.9670 degrees.As the simulated data is randomized, your numbers may differ slightly and from run to run.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 03:48:45 pm
content: The above routine, running unmodified on a STM32L152, at 32Mhz, estimated the impedance + phase shift of a 1uf capacitor + 1ohm ESR at 100Hz:impedance is estimated as 1591.55ohm, vs. 1591.5498ohm actual;phase shift is estimated as 1.570198 radians (=89.96572 degrees), vs. 89.9611 degrees actual.Total cycle count is 238K, or 7.4 milliseconds.Flash footprint is about 1KB, not counting the floating point overhead.Not bad.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 07:55:50 pm
content: QuoteTo expand on that, if you multiply the voltage measurements vs. a reference signal (say a sine signal), integrate the product over a cycle and you get the real part of the complex impedance (in rms); If you multiply the same voltage measurements vs. a 90 degree offset signal (cos() for example), you get the imaginary part of the impedance. From the two, you can calcuate the rms + phase angle.If you do that to the voltage and the current signal, you get the impedance + relative phase shift.Code://calculate impedance and phasevoid othog_f(float * volt, float *amp, float *impedance, float *phase, int block_size) {float volt_rms, volt_phase;float amp_rms, amp_phase;othogrms_f(volt, &volt_rms, &volt_phase, block_size);//calculate volt's rms and phase (vs. reference signal)othogrms_f(amp, &amp_rms, &amp_phase, block_size);//calculate amp's rms and phase (vs. reference signal)*impedance = volt_rms / amp_rms * AMP_GAIN;//calculate impedance*phase = volt_phase - amp_phase;//calculate relative phase shiftif (*phase > PI/2) *phase -=PIx2;//limit phase to +/-90 degreesif (*phase <-PI/2) *phase +=PIx2;}Othogrms_f() performs the calculate that multiplying the measurements by sine() and cosine() to get to its rms and phase angle. Once that's done, calculating impedance + relative phase shift is easy.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 08:02:29 pm
content: Here is a comparison of the orthogonal appoach vs. the digital synchronous approach:The actual parametres are highlighted by the red arrow, followed by the parameters followed by the digital synchronous detection discussed earlier. The green arrow indicates the parameters estimated by the orthogonal multiplification.As you can see, they are very close.If you think about it, the digital synchronous detection approach is simply a special case of the orthogonal multiplification: rather than measuring the phase shift from a reference signal, it assumes one of the measurements (voltage or current) is the reference signal and measures the other's phase shift from the presummed reference signal.The orthogonal multiplification is prettier programming wise, but is less accurate and slower, due to two rounds of multiplification.

author: dannyf
author_rank: Super Contributor
timestamp: January 02, 2015, 11:09:31 pm
content: I ported the code to STM8S, PIC24F, and STM32F030, in addition to STM32L152 that I started the project with, trying to get a sense as to flash / ram needed to run the project.Flash space: So far, the biggest foot print comes from STM32L152/F030: 6KB to 12KB, depending on what's included, followed by STM8S (8KB), and the smallest is PIC24F (<5KB).RAM space is consistently 5 - 6KB.Execution cycles vary, from 100K cycles on PIC24F to 200K+ cycles on STM32L152.I think I will need 2KB for the DAC routines, 2-3KB for the ADC routines. Maybe another 1-2KB RAM for the adc/buffer. So a total of 16KB flash + 8KB ram seem to be doable.It also seems that doing it on 8-bit mcus is totally possible, assuming you have sufficient ram space and are willing to utilize outboard adc (+pga).STM32F030F seems to be a nice compromise here, with its onboard 12-bit adc. But a lack of DMA/DAC means you have to run it at a fixed frequency.

author: fonak
author_rank: Contributor
timestamp: July 02, 2017, 06:30:19 pm
content: Hello dannyfCan you share the code for PIC24F?regards

author: Kalvin
author_rank: Super Contributor
timestamp: July 02, 2017, 06:41:46 pm
content: dannyf has not been active for some time now, but you should be able to contact him in his wordpress site mentioned in his profile signature.