kopia lustrzana https://github.com/peterhinch/micropython-samples
163 wiersze
6.8 KiB
Markdown
163 wiersze
6.8 KiB
Markdown
# 1. Introduction
|
||
|
||
The principal purpose of this application note is to describe a technique for
|
||
measuring the relative phase of a pair of sinsusoidal signals over the full
|
||
range of 2π radians (360°). This is known as quadrature detection; while
|
||
ancient history to radio engineers the method may be unfamiliar to those from
|
||
a programming background.
|
||
|
||
## 1.1 Measurement of relative timing and phase of analog signals
|
||
|
||
As of 11th April 2018 the Pyboard firmware has been enhanced to enable multiple
|
||
ADC channels to be read in response to a timer tick. At each tick a reading is
|
||
taken from each ADC in quick succession. This enables the relative timing or
|
||
phase of signals to be measured. This is facilitated by the static method
|
||
`ADC.read_timed_multi` which is documented
|
||
[here](http://docs.micropython.org/en/latest/pyboard/library/pyb.ADC.html).
|
||
|
||
The ability to perform such measurements substantially increases the potential
|
||
application areas of the Pyboard, supporting precision measurements of signals
|
||
into the ultrasonic range. Applications such as ultrasonic rangefinders may be
|
||
practicable. With two or more microphones it may be feasible to produce an
|
||
ultrasonic active sonar capable of providing directional and distance
|
||
information for multiple targets.
|
||
|
||
I have used it to build an electrical network analyser which yields accurate
|
||
gain and phase (+-3°) plots of signals up to 40KHz.
|
||
|
||
# 2 Applications
|
||
|
||
## 2.1 Measurements of relative timing
|
||
|
||
In practice `ADC.read_timed_multi` reads each ADC in turn. This implies a delay
|
||
between each reading. This was estimated at 1.8μs on a Pyboard V1.1. This value
|
||
can be used to compensate any readings taken.
|
||
|
||
## 2.2 Phase measurements
|
||
|
||
### 2.2.1 The quadrature detector
|
||
|
||
The principle of a phase sensitive detector (applicable to linear and sampled
|
||
data systems) is based on multiplying the two signals and low pass filtering
|
||
the result. This derives from the prosthaphaeresis formula:
|
||
|
||
sin a sin b = (cos(a-b) - cos(a+b))/2
|
||
|
||
If
|
||
ω = angular frequency in rad/sec
|
||
t = time
|
||
ϕ = phase
|
||
this can be written:
|
||
|
||
sin ωt sin(ωt + ϕ) = 0.5(cos(-ϕ) - cos(2ωt + ϕ))
|
||
|
||
The first term on the right hand side is a DC component related to the relative
|
||
phase of the two signals. The second is an AC component at twice the incoming
|
||
frequency. So if the product signal is passed through a low pass filter the
|
||
right hand term disappears leaving only 0.5cos(-ϕ).
|
||
|
||
Where the frequency is known the filtering may be achieved simply by averaging
|
||
over an integer number of cycles.
|
||
|
||
For the above to produce accurate phase measurements the amplitudes of the two
|
||
signals must be normalised to 1. Alternatively the amplitudes may be measured
|
||
and the DC phase value divided by their product.
|
||
|
||
Because cos ϕ = cos -ϕ this can only detect phase angles over a range of π
|
||
radians. To achieve detection over the full 2π range a second product detector
|
||
is used with one signal phase-shifted by π/2. This allows a complex phasor
|
||
(phase vector) to be derived, with one detector providing the real part and the
|
||
other the imaginary one.
|
||
|
||
In a sampled data system where the frequency is known, the phase shifted signal
|
||
may be derived by indexing into one of the sample arrays. To achieve this the
|
||
signals must be sampled at a rate of 4Nf where f is the frequency and N is an
|
||
integer >= 1. In the limiting case where N == 1 the index offset is 1; this
|
||
sampling rate is double the Nyquist rate.
|
||
|
||
In practice phase compensation may be required to allow for a time delay
|
||
between sampling the two signals. If the delay is T and the frequency is f, the
|
||
phase shift θ is given by
|
||
|
||
θ = 2πfT
|
||
|
||
Conventionally phasors rotate anticlockwise for leading phase. A time delay
|
||
implies a phase lag i.e. a negative phase or a clockwise rotation. If λ is the
|
||
phasor derived above, the adjusted phase α is given by multiplying by a phasor
|
||
of unit magnitude and phase -θ:
|
||
|
||
α = λ(cos θ - jsin θ)
|
||
|
||
For small angles (i.e. at lower frequencies) this approximates to
|
||
|
||
α ~= λ(1 - jθ)
|
||
|
||
### 2.2.2 A MicroPython implementation
|
||
|
||
The example below, taken from an application, uses quadrature detection to
|
||
accurately measure the phase difference between an outgoing sinewave produced
|
||
by `DAC.write_timed` and an incoming response signal. For application reasons
|
||
`DAC.write_timed` runs continuously. Its output feeds one ADC and the incoming
|
||
signal feeds another. The ADC's are fed from matched hardware anti-aliasing
|
||
filters; the matched characteristic ensures that any phase shift in the filters
|
||
cancels out.
|
||
|
||
Because the frequency is known the ADC sampling rate is chosen so that an
|
||
integer number of cycles are captured. Thus averaging is used to remove the
|
||
double frequency component.
|
||
|
||
The function `demod()` returns the phase difference in radians. The sample
|
||
arrays are globals `bufout` and `bufin`. The `freq` arg is the ADC sampling
|
||
frequency and is used to provide phase compensation for the delay mentioned in
|
||
section 2.1.
|
||
|
||
The arg `nsamples` is the number of samples per cycle of the sinewave. As
|
||
described above it can be any integer multiple of 4.
|
||
|
||
```python
|
||
from math import sqrt, pi, sin, cos
|
||
import cmath
|
||
_ROOT2 = sqrt(2)
|
||
|
||
# Return RMS value of a buffer, removing DC.
|
||
def amplitude(buf):
|
||
buflen = len(buf)
|
||
meanin = sum(buf)/buflen
|
||
return sqrt(sum((x - meanin)**2 for x in buf)/buflen)
|
||
|
||
def demod(freq, nsamples):
|
||
sum_norm = 0
|
||
sum_quad = 0 # quadrature pi/2 phase shift
|
||
buflen = len(bufin)
|
||
assert len(bufout) == buflen, 'buffer lengths must match'
|
||
meanout = sum(bufout)/buflen # ADC samples are DC-shifted
|
||
meanin = sum(bufin)/buflen
|
||
# Remove DC offset, calculate RMS and convert to peak value (sine assumption)
|
||
# Aim: produce sum_norm and sum_quad in range -1 <= v <= +1
|
||
peakout = amplitude(bufout) * _ROOT2
|
||
peakin = amplitude(bufin) * _ROOT2
|
||
# Calculate phase
|
||
delta = int(nsamples // 4) # offset for pi/2
|
||
for x in range(buflen):
|
||
v0 = (bufout[x] - meanout) / peakout
|
||
v1 = (bufin[x] - meanin) / peakin
|
||
s = (x + delta) % buflen # + pi/2
|
||
v2 = (bufout[s] - meanout) / peakout
|
||
sum_norm += v0 * v1 # Normal
|
||
sum_quad += v2 * v1 # Quadrature
|
||
|
||
sum_norm /= (buflen * 0.5) # Factor of 0.5 from the trig formula
|
||
sum_quad /= (buflen * 0.5)
|
||
c = sum_norm + 1j * sum_quad # Create the complex phasor
|
||
# Apply phase compensation measured at 1.8μs
|
||
theta = 2 * pi * freq * 1.8e-6
|
||
c *= cos(theta) - 1j * sin(theta)
|
||
return cmath.phase(c)
|
||
```
|
||
Note that the phase compensation figure of 1.8μs is based on empirical
|
||
measurement on a Pyboard 1.x. The easiest way to measure the time difference
|
||
between samples is to measure the phase error between identical fast sinewaves.
|
||
This was done by feeding the same signal into both ADC's (with compensation
|
||
disabled) and measuring the reported phase difference. Then
|
||
T = θ/(2πf) where f is the signal frequency.
|