ADC processing and CMSIS DSP library - Page 1
Forum section: electronics / general

author: Red_Micro
author_rank: Regular Contributor
timestamp: September 18, 2020, 03:42:19 pm
content: I'm using a STM32 Cortex M0+ to read an AC signal from a CT. I'm sampling at 6kHz and storing 400 samples. The signal has a DC bias equal to Vcc/2 = 1.65V. In the digital domain this is 2048. When there's no input signal, I only read the DC bias which is OK. Have a look at the dc_bias_raw.txt file. As you can see, the readings are not so bad. It varies from 2044 to 2052. The DC bias is very precise in hardware so I think this variation is due to the noise floor. Now if I want to do RMS in that set of data, I need to find a way to deal with this DC bias variation. In the file adc_raw_current.txt you can see the samples when there's an input signal.I have been thinking the following:1- Subtract a fixed value of 2048 from each ADC reading. This is no so good as I said above this value may vary slightly. Also, if I want to read zero cross it may cause errors to choose exactly 2048 as reference.2- Sample the DC bias and average it.3- Leave it and get the DC value as RMS result when there's no input signal4- Use a more sophisticated software high pass filterIn the other hand, as the title of the post says, I want to useCMSIS DSP Library.This library allows you to use floating point calculations, and fixed point in Q15 and Q31 formats. The controller I'm using does not have FPU, so I think I will go with fixed point, but how I convert my set of data to Q15 or Q31? Also I do not need much precision, I'm even thinking of staying in the integer domain.Any help is appreciated!

author: Renate
author_rank: Super Contributor
timestamp: September 20, 2020, 11:52:06 am
content: Looking at your numbers, you've got a 12 bit ADC and you're using a range of about 6 bits.Are you running this with a tiny load and not the real load?If that is supposed to be the real load, then:You might not be using the right current transformer for your applicationYou might have the wrong ballast resistorYou might need to throw a preamp at thisThat CMSIS DSP library won't do you a lot of good because it wants all your data at the same time.To do a simple RMS just accumulate the square of your samples as you go along.When you have a bunch of them, divide by count then do the square root.This means that your ADC ISR just takes in the ADC value, subtracts 2048, squares it for 24 bit fixed (ok, 32 bit if you're not using assembly).Once a second (or whenever you do the heavy lifting of a divide (might just be a shift or a multiply) and a square root.If you want to keep a running offset, also accumulate the straight values of your samples as you go along.You can fix the accumulated squares with that value when you're done.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 20, 2020, 12:05:19 pm
content: Wanting to use CMSIS DSP library sounds like a typical X-Y problemCMSIS DSP library is a joke. Completely trivial math operations done in very limited and fixed ways. Sure, if you happen to have all data in buffers, in the right format, at once, anyway, and would just call a processing function once anyway, then you may as well use the library (and save writing the 5 lines of code function yourself). Although it still likely takes 10-100 times more time and commitment to read the library documentation, include the files, and use it.Otherwise than that, usually you need realtime processing. Just write the math operations you want yourself. I think you were given some good and efficient code examples the last time you asked about the problem, what's wrong using them?AFAIK this library does not implement complex algorithms (think about computer vision, or similar) and you wouldn't want any complex algorithm there anyway.If you want to have the DC value of the signal, combining the actual DC existing in the signal, and any errors in your measurement:Assuming you can decide you have either 50Hz or 60Hz signal, you can simply keep track of the average value over, say, 16 cycles (to reduce offset caused by frequency error by factor of 16), and subtract that. If you cannot sync to the signal, then you need a much longer average to subtract - or a FIR/IIR highpass/bandpass filter but that's going to need a lot of taps, too.If you only want your measurement error, there is no other way than to model your error sources and do periodical calibrations, for which you need to be able to disconnect the load. (You could disconnect the measurement path, but then you would be calibrating only part of the system. Disconnecting the actual load would also leave the current transformer in the loop, to be compensated.)And also Renate's right, do maximize the dynamic range of the current transformer and ADC. Try to make your maximum possible load signal to range from, say, 500 to 4096-500.

author: coppice
author_rank: Super Contributor
timestamp: September 20, 2020, 12:45:57 pm
content: Quote from: Siwastaja on September 20, 2020, 12:05:19 pmI agree. Its like they felt they needed to have a DSP library to help them promote the DSP qualities of some of their architectures, but either didn't understand the problem or didn't want to invest.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 20, 2020, 01:23:56 pm
content: Decision makers need checkboxes to check in order to produce Powerpoint slides and purchase decisions. This is all OK. CMSIS DSP library exists for this purpose.Then engineers can use the parts as actually intended, as long as the decision maker doesn't micromanage and prevent work being done by requiring usage of the CMSIS DSP library.They also need some "standardized" DSP code so they can run benchmarks and publish numbers (clock cycles / operation, and so on).

author: Renate
author_rank: Super Contributor
timestamp: September 20, 2020, 01:24:56 pm
content: Quote from: Renate on September 20, 2020, 11:52:06 amSpecifically:RMS = sqrt(Σ(x^2)/n - Σ(x)^2/n^2)Looks familiar? It's standard deviation.Edit: Don't just work with the raw value, subtract 2048 to start to get the mean close to zero for better computations.

author: Red_Micro
author_rank: Regular Contributor
timestamp: September 20, 2020, 02:12:57 pm
content: Quote from: Renate on September 20, 2020, 11:52:06 amMy load is variable, and the samples in the adc_raw_current.txt file, is for my lowest load. See the attached the plot. Also, my resolution is halved as the signal is riding a 1.65V DC bias. That's why my pre amp has adjustable gain.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: September 20, 2020, 02:39:25 pm
content: Remove the DC with a highpass. A single pole is easy enough, or you can use as many as you like. Set the pole somewhat below the CT's frequency response, I suppose -- that will avoid additional phase shift.Example:Code:int16_t highpass(int16_t samp) {static int32_t hp = 0;samp -= hp >> 16;hp += (int32_t)samp * HIGHPASS_FEEDBACK;return samp;}Obviously with the static local variable, this isn't reentrant or sharable across channels, it's a single use thing. Example use:Code:samp = adc_result();samp = highpass(samp);...Uses fixed point signed format. For ARM, the int16s should probably be changed to 32. Which gets you a ton more dynamic range, pick whatever fixed point you like. Most immediate benefit being, substantial rounding is deferred until the end (when the result finally hits a DAC, or display or other readout method).Which, for doing RMS, notice that squaring a 32-bit number gives a 64-bit result; but if it's fully fractional (1.31 signed), it only needs one shift to restore that format, and the low half can be truncated off without much* loss of SNR. The products are summed in an accumulator, then some time later, you sample the accumulator (then clear it), divide by count (if this is a 2^N decimation, merely a shift is needed) and square-root. Or heck, you can use a 64-bit accumulator for all that matters, you should have plenty of CPU cycles to spare and another DWORD is hardly a lot of memory.*Or really, if your input is only 12 bit, and there's no oversampling to tease extra bits out of it, then 12 bits squared is 24 bits, still well within a 32-bit word -- no loss at all.Tim

author: coppice
author_rank: Super Contributor
timestamp: September 20, 2020, 03:24:14 pm
content: Quote from: T3sl4co1l on September 20, 2020, 02:39:25 pmYou really want something like:Code:int16_t dc_filter16(int16_t x){int32_t state;*p += ((((int32_t) x << 16) - *p) >> 14);x -= (*p >> 16);return x;}so, you noise shape the result of the pole (i,e. the variable 'state', which is an estimate of the DC), before it gets applied to the signal. There is no need to use a multiply, as the pole's location is not critical. A simple shift is fine. On an ARM this may not matter much, but if you use a MCU with a high cost for multiplies it may. This example shifts by 14, but that's a compromise you can adjust. A bigger shift gives a smoother DC estimation, but it takes longer for the filter to settle. That settling time may be important if you need to start up and get an accurate RMS value quickly. Also, remember that the result of the pole, well above its turnover point, will be about 90 degrees out of phase with the signal. So, the residual ripple on the DC estimate being removed from the signal is in quadrature with the signal, and can nudge its phase. If you are only generating an RMS result, this may not matter. If you are doing other things, like also using a voltage signal so you can measure power, this phase shift must be allowed for.

author: Renate
author_rank: Super Contributor
timestamp: September 20, 2020, 03:44:42 pm
content: Quote from: T3sl4co1l on September 20, 2020, 02:39:25 pmReally?The DC is a very slow long-term drift.I still like my std. dev.It's also (minorly) less computation to just deal with it at the end of the sample batch.

author: coppice
author_rank: Super Contributor
timestamp: September 20, 2020, 03:56:33 pm
content: Quote from: Renate on September 20, 2020, 03:44:42 pmYour approach has the benefit of zero settling time, but it produces a poorer estimate of the DC.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: September 20, 2020, 04:07:25 pm
content: first highpass filter as Ts says.do{highpassfilteredvalue = highpassfilter(input_sample);squared = highpassfilteredvalue*highpassfilteredvalue;// low pass filter the squared valuesquaredAverage = squaredAverage + K*(squared - squaredAverage); // K < 1RMS_average = sqrt(squaredAverage);}