I'm working on a side project that involves comparing ADC samples from a Waveform generator to calculations from my embedded device (in C).
The device takes continuous samples from a waveform generator, with the following settings:
Sine Wave (60 Hz)
2.1 Vpk-Vpk
1.25 Vdc offset
On my device, I have a sine function that calculates the exact same data:
double sine_wave(double amplitude, int freq, double time, int phase_number,
double offset)
{
double voltage;
double rad = 2 * M_PI * freq * time;
voltage = amplitude * sin(rad) + offset;
return voltage;
}
Where FREQ = 60,
amplitude = 1.05
Offset = 1.25.
Time is the parameter to an abstracted sine function: stop_timer - start_timer.
Where it it is the time elapsed before and after a single ADC sample has been polled.
In my device handler, I am running (pseduo code)
while (1) {
Find_zero_crossing_point(); /* 1.25 is the midpoint */
if (ADC_get_sample() == zero_crossing && (Adc_sample is increasing)) { /* start timer at zero-crossing point */
start_timer(); /* get timestamp of initial point so it can be compared throughout */
while (1) {
stop_timer();
measured_data = ADC_get_sample();
expected_data = abstracted_sine_wave((stop_timer - start_timer));
compare_both_value(measured_data, expected_data);
}
To get stop_timer and start_timer, I'm using the timespec struct. And calling it with clock_gettime(CLOCK_MONOTONIC, &stop_timer);
Now the problem isn't the implementation. I'm able to get the values to match up for the most part. The issue is the the sampling rate is not uniform. And I believe this is due to the nature of running it inside a while loop.
After running this for about 30 seconds, or approximately 810,000 samples (30 x 27,000 samples), the little deviation adds up and the values deviate too much for it to serve the purpose of this project.
I was wondering if you have a solution where I can keep the timing in sync throughout the entirely of the device running this infinite loop?
Thanks!! (I've spent days trying to crack this but nothing is coming to mind)
Related
I'm testing and performing simple FFT's and I'm interested in phase shift.
I geneate simple array of 256 samples with sinusoid with 10 cycles.
I perform an FFT of those samples and receiving complex data (2x128).
Than I calculate magnitude of those data and FFT looks like expected:
Then I want to calculate phase shift from fft complex output. I'm using atan2.
Combined output fft_magnitude (blue) + fft+phase (red) looks like this:
This is pretty much what I expect with a "small" problem. I know this is wrapping but if I imagine unwrapping it, the phase shift in the magnitude peak is reading 36 degrees and I think it should be 0 because my input sinusiod was not shifted at all.
If I shift this -36 deg (blue is in-phase, red is shifted, blue is printed only for reference) the sinusiod looks like this:
And than if I perform an FFT of this red data the magnitude + phase output looks like this:
So it is easy to imagine that unwrapped phase will be close to 0 at the magniture peak.
So there is 36 deg offset. But what happenes if I prepare data with 20 cycles per 256 samples and 0 phase shift
If I then perform an FFT, this is an output (magnitude + phase):
And I can tell you if will cross the peak point at 72 degrees. So there is now 72 degrees offset.
Can anyone give me a hint why is that happening?
Is it right that atan2() phase output is frequency dependent with offset of 2pi/cycles (360 deg/cycles) ?
How to unwrap it and get correct results (I couldn't find working C library to unwrap).
This is running on ARM Cortex-M7 processor (embedded).
#define phaseShift 0
#define cycles 20
#include <arm_math.h>
#include <arm_const_structs.h>
float32_t phi = phaseShift * PI / 180; //phase shift in radians
float32_t data[256]; //input data for fft
float32_t output_buffer[256]; //output buffer from fft
float32_t phase_data[128]; //will contain atan2 values of output from fft (output values are complex)
float32_t magnitude[128]; //will contain absolute values of output from fft (output values are complex)
float32_t incrFactorRadians = cycles * 2 * PI / 255;
arm_rfft_fast_instance_f32 RealFFT_Instance;
void setup()
{
Serial.begin(115200);
delay(2000);
arm_rfft_fast_init_f32(&RealFFT_Instance, 256); //initializing fft to be ready for 256 samples
for (int i = 0; i < 256; i++) //print sinusoids
{
data[i] = arm_sin_f32(incrFactorRadians * i + phi);
Serial.print(arm_sin_f32(incrFactorRadians * i), 8); Serial.print(","); Serial.print(data[i], 8); Serial.print("\n"); //print reference in-phase sinusoid and shifted sinusoid (data for fft)
}
Serial.print("\n\n");
delay(10000);
arm_rfft_fast_f32(&RealFFT_Instance, data, output_buffer, 0); //perform fft
for (int i = 0; i < 128; i++) //calculate absolute values of an fft output (fft output is complex), and phase shift
{
magnitude[i] = output_buffer[i * 2] * output_buffer[i * 2] + output_buffer[(i * 2) + 1] * output_buffer[(i * 2) + 1];
__ASM("VSQRT.F32 %0,%1" : "=t"(magnitude[i]) : "t"(magnitude[i])); //fast square root ARM DSP function
phase_data[i] = atan2(output_buffer[i * 2], output_buffer[i * 2 +1]) * 180 / PI;
}
}
void loop() //print magnitude of fft and phase output every 10 seconds
{
for (int i = 0; i < 128; i++)
{
Serial.print(magnitude[i], 8); Serial.print(","); Serial.print(phase_data[i], 8); Serial.print("\n");
}
Serial.print("\n\n");
delay(10000);
}
To break down the excellent answer by hotpaw2. (Their answers are always so loaded with golden nuggets of information that I spend days learning enough to comprehend the brilliance.)
When an engineer says "integer periodic" they mean your samples that you are feeding into the FFT (the aperture) needs to sample in a way the ensures you capture one full wave of the frequency sin wave.
Think of the sin wave starting at zero and cresting at one then falling below zero into the trough at negative one and then coming back up to zero.
This is one "full cycle". Now if your wave has a period of 10 cycles per second and you sample at 100 samples per second you will have 10 samples per wave.
So now you put 13 samples into an FFT and your phase is off. Why?
Well the phase is looking for the wave to smoothly continue forever. You just started a zero for the first sample and dropped off as .25 on the 13th sample. Now the phase calculation tries to connect the two ends and has this jump in the wave. This causes the phase to come out wrong.
What you need to do is select a number of samples to feed into your FFT that you know will contain full waves only.
(NOTE) You are only concerned with the phase of one freq at a time.
AND your sample aperture must not start and end at the sin waves same point.
IF you start at zero and end at zero the calculation pasting the two ends together in a forever circle will get two zeros at the transition. So you have to stop one sample short of the repeat point.
Code demonstrating this can be found: Scipy FFT - how to get phase angle
An bare FFT plus an atan2() only correctly measures the starting phase of an input sinusoid if that sinusoid is exactly integer periodic in the FFT's aperture width.
If the signal is not exactly integer periodic (some other frequency), then you have to recenter the data by doing an FFTshift (rotate the data by N/2) before the FFT. The FFT will then correctly measure the phase at the center of the original data, and away from the circular discontinuity produced by the FFT's finite length rectangular window on non-periodic-in-aperture signals.
If you want the phase at some point in the data other than the center, you can use the estimate of the frequency and phase at the center to recalculate the phase at other positions.
There are other window functions (Blackman-Nutall, et.al.) that might produce a better phase estimate than a rectangular window, but usually not as good an estimate as using an FFTShift.
I need to implement an RMS calculations of sine wave in MCU (microcontroller, resource constrained). MCU lacks FPU (floating point unit), so I would prefer to stay in integer realm. Captures are discrete via 10 bit ADC.
Looking for a solution, I've found this great solution here by Edgar Bonet: https://stackoverflow.com/a/28812301/8264292
Seems like it completely suits my needs. But I have some questions.
Input are mains 230 VAC, 50 Hz. It's transformed & offset by hardware means to become 0-1V (peak to peak) sine wave which I can capture with ADC getting 0-1023 readings. Hardware are calibrated so that 260 VRMS (i.e. about -368:+368 peak to peak) input becomes 0-1V peak output. How can I "restore" back original wave RMS value providing I want to stay in integer realm too? Units can vary, mV will do fine also.
My first guess was subtracting 512 from the input sample (DC offset) and later doing this "magic" shift as in Edgar Bonet answer. But I've realized it's wrong because DC offset aren't fixed. Instead it's biased to start from 0V. I.e. 130 VAC input would produce 0-500 mV peak to peak output (not 250-750 mV which would've worked so far).
With real RMS to subtract the DC offset I need to subtract squared sum of samples from the sum of squares. Like in this formula:
So I've ended up with following function:
#define INITIAL 512
#define SAMPLES 1024
#define MAX_V 368UL // Maximum input peak in V ( 260*sqrt(2) )
/* K is defined based on equation, where 64 = 2^6,
* i.e. 6 bits to add to 10-bit ADC to make it 16-bit
* and double it for whole range in -peak to +peak
*/
#define K (MAX_V*64*2)
uint16_t rms_filter(uint16_t sample)
{
static int16_t rms = INITIAL;
static uint32_t sum_squares = 1UL * SAMPLES * INITIAL * INITIAL;
static uint32_t sum = 1UL * SAMPLES * INITIAL;
sum_squares -= sum_squares / SAMPLES;
sum_squares += (uint32_t) sample * sample;
sum -= sum / SAMPLES;
sum += sample;
if (rms == 0) rms = 1; /* do not divide by zero */
rms = (rms + (((sum_squares / SAMPLES) - (sum/SAMPLES)*(sum/SAMPLES)) / rms)) / 2;
return rms;
}
...
// Somewhere in a loop
getSample(&sample);
rms = rms_filter(sample);
...
// After getting at least N samples (SAMPLES * X?)
uint16_t vrms = (uint32_t)(rms*K) >> 16;
printf("Converted Vrms = %d V\r\n", vrms);
Does it looks fine? Or am I doing something wrong like this?
How does SAMPLES (window size?) number relates to F (50Hz) and my ADC capture rate (samples per second)? I.e. how much real samples do I need to feed to rms_filter() before I can get real RMS value providing my capture speed are X sps? At least how to evaluate required minimum N of samples?
I did not test your code, but it looks to me like it should work fine.
Personally, I would not have implemented the function this way. I would
instead have removed the DC part of the signal before trying to
compute the RMS value. The DC part can be estimated by sending the raw
signal through a low pass filter. In pseudo-code this would be
rms = sqrt(low_pass(square(x - low_pass(x))))
whereas what you wrote is basically
rms = sqrt(low_pass(square(x)) - square(low_pass(x)))
It shouldn't really make much of a difference though. The first formula,
however, spares you a multiplication. Also, by removing the DC component
before computing the square, you end up multiplying smaller numbers,
which may help in allocating bits for the fixed-point implementation.
In any case, I recommend you test the filter on your computer with
synthetic data before committing it to the MCU.
How does SAMPLES (window size?) number relates to F (50Hz) and my ADC
capture rate (samples per second)?
The constant SAMPLES controls the cut-off frequency of the low pass
filters. This cut-off should be small enough to almost completely remove
the 50 Hz part of the signal. On the other hand, if the mains
supply is not completely stable, the quantity you are measuring will
slowly vary with time, and you may want your cut-off to be high enough
to capture those variations.
The transfer function of these single-pole low-pass filters is
H(z) = z / (SAMPLES * z + 1 − SAMPLES)
where
z = exp(i 2 π f / f₀),
i is the imaginary unit,
f is the signal frequency and
f₀ is the sampling frequency
If f₀ ≫ f (which is desirable for a good sampling), you can approximate
this by the analog filter:
H(s) = 1/(1 + SAMPLES * s / f₀)
where s = i2πf and the cut-off frequency is f₀/(2π*SAMPLES). The gain
at f = 50 Hz is then
1/sqrt(1 + (2π * SAMPLES * f/f₀)²)
The relevant parameter here is (SAMPLES * f/f₀), which is the number of
periods of the 50 Hz signal that fit inside your sampling window.
If you fit one period, you are letting about 15% of the signal through
the filter. Half as much if you fit two periods, etc.
You could get perfect rejection of the 50 Hz signal if you design a
filter with a notch at that particular frequency. If you don't want
to dig into digital filter design theory, the simplest such filter may
be a simple moving average that averages over a period of exactly
20 ms. This has a non trivial cost in RAM though, as you have to
keep a full 20 ms worth of samples in a circular buffer.
I'm trying to control a Servo Motor with a PIC18f4550, but before buying one, I'm trying to simulate it on Proteus ISIS, but I'm getting some inconsistencies when setting the angle.
I've tried using a 20ms period and 1ms, 1.167ms, 1.333ms, 1.5ms, 1.667ms or 1.833ms duty cycle and it results in a -69.8° Angle on Proteus MOTOR-PWMSERVO, but using a 2ms duty cycle results in a full 90.0° Angle
#define CONTROL PORTCbits.RC0
#define BUTTON PORTCbits.RC1
const Ang_Neg90 = 1.0;
const Ang_Neg60 = 1.167;
const Ang_Neg30 = 1.333;
const Ang_0 = 1.5;
const Ang_30 = 1.667;
const Ang_60 = 1.833;
const Ang_90 = 2.00;
// ------------------------------------------------- //
void ServoPosition (unsigned float Angle) {
CONTROL = 1;
Delay_ms(Angle);
CONTROL = 0;
Delay_ms(20 - Angle);
}
void main(){
while(1){
ServoPosition(Ang_0);
if (BUTTON == 1){
break;
while(1){
ServoPosition(Ang_90);
}
}
}
}
I want my program to be able to go from 0 degrees to 90 degrees, but all I'm getting is -69.8 to 90. Any ideas of what I'm getting wrong?
It's hard to tell without seeing your entire setup, so here's a list of things it might be.
Check Simulated Servo Settings
Make sure it's set to the default values.
Measure your PWM signal with the Oscilloscope Tool
The Hobby Servo Motor sample shows how to use the oscilloscope tool. Hook the oscilloscope up to your pic18f4550 PWM output and measure it to make sure the duty cycle is correct. You're having problems at the low end (trying to reach 1ms) so I suspect the oscilloscope will show your duty cycle is too long.
If you find that your duty cycles are too long, you can try:
Subtracting a fudge value to deal with whatever falloff time is present in the simulation. You might have to use different values when you run on real hardware.
Using a dedicated PWM module on the pic18f4550. This is probably the best solution since you can just set the PWM registers (assuming this is how it works... I haven't looked closely at the manual) and then let it control the duty cycle for you.
Check Voltages
The battery in the Hobby Server was 6V, but 5V also seemed to work. Lower than that and it began to act strange.
The type of your constant is wrong:
const Ang_Neg90 = 1.0;
This is a constant of type int with the value 1.
I guess you mean:
const float Ang_Neg90 = 1.0;
But anyway:
If you use the Microchip built in delay function the argument should be of the type unsigned long and you are working with unsigned float.
Try to define your delay times in us (integer not float values!) and use the function __delay_us(...). And be sure to define _XTAL_FREQ correctly.
And as already mentioned: Please remember there don't exist a type unsigned float. float is always unsigned.
I make a function like this
trace_printk("111111");
udelay(4000);
trace_printk("222222");
and the log shows it's 4.01 ms , it'OK
But when i call like this
trace_printk("111111");
ndelay(10000);
ndelay(10000);
ndelay(10000);
ndelay(10000);
....
....//totally 400 ndelay calls
trace_printk("222222");
the log will shows 4.7 ms. It's not acceptable.
Why the error of ndelay is so huge like this?
Look deep in the kernel code i found the implemention of this two functions
void __udelay(unsigned long usecs)
{
__const_udelay(usecs * 0x10C7UL); /* 2**32 / 1000000 (rounded up) */
}
void __ndelay(unsigned long nsecs)
{
__const_udelay(nsecs * 0x5UL); /* 2**32 / 1000000000 (rounded up) */
}
I thought udelay will be 1000 times than ndelay, but it's not, why?
As you've already noticed, the nanosecond delay implementation is quite a coarse approximation compared to the millisecond delay, because of the 0x5 constant factor used. 0x10c7 / 0x5 is approximately 859. Using 0x4 would be closer to 1000 (approximately 1073).
However, using 0x4 would cause the ndelay to be less than the number of nanoseconds requested. In general, delay functions aim to provide a delay at least as long as requested by the user (see here: http://practicepeople.blogspot.jp/2013/08/kernel-programming-busy-waiting-delay.html).
Every time you call it, a rounding error is added. Note the comment 2**32 / 1000000000. That value is really ~4.29, but it was rounded up to 5. That's a pretty hefty error.
By contrast the udelay error is small: (~4294.97 versus 4295 [0x10c7]).
You can use ktime_get_ns() to get high precision time since boot. So you can use it not only as high precision delay but also as high precision timer. There is example:
u64 t;
t = ktime_get_ns(); // Get current nanoseconds since boot
for (i = 0; i < 24; i++) // Send 24 1200ns-1300ns pulses via GPIO
{
gpio_set_value(pin, 1); // Drive GPIO or do something else
t += 1200; // Now we have absolute time of the next step
while (ktime_get_ns() < t); // Wait for it
gpio_set_value(pin, 0); // Do something, again
t += 1300; // Now we have time of the next step, again
while (ktime_get_ns() < t); // Wait for it, again
}
I'm testing one code provided from my colleague and I need to measure the time of execution of one routine than performs a context switch (of threads).
What's the best choice to measure the time? I know that is available High Resolution Timers like,
QueryPerformanceCounter
QueryPerformanceFrequency
but how can I translate using that timers to miliseconds or nanoseconds?
LARGE_INTEGER lFreq, lStart;
LARGE_INTEGER lEnd;
double d;
QueryPerformanceFrequency(&lFreq);
QueryPerformanceCounter(&lStart);
/* do something ... */
QueryPerformanceCounter(&lEnd);
d = ((doublel)End.QuadPart - (doublel)lStart.QuadPart) / (doublel)lFreq.QuadPart;
d is time interval in seconds.
As the operation than i am executing is in order of 500 nanos, and the timers doens't have precision, what i made was,
i saved actual time with GetTickCount() - (Uses precision of ~ 12milis) and performs the execution of a route N_TIMES (Number of times than routine executed) than remains until i press something on console.
Calculate the time again, and make the difference dividing by N_TIMES, something like that:
int static counter;
void routine()
{
// Operations here..
counter++;
}
int main(){
start <- GetTickCount();
handle <- createThread(....., routine,...);
resumeThread(handle);
getchar();
WaitForSingleObject(handle, INFINITE);
Elapsed = (GetTickCount() - start) * 1000000.0) / counter;
printf("Nanos: %d", elapsed);
}
:)