Skip to content

Commit

Permalink
Move NCO delay inline
Browse files Browse the repository at this point in the history
This allows the first phase offset to always be `offset`. If a frequency was passed in `__call__()`, the initial phase offset was advanced by the phase increment. This is also more consistent with textbooks and MATLAB.
  • Loading branch information
mhostetter committed Jan 7, 2024
1 parent 48a8f8a commit 4c3954c
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 48 deletions.
66 changes: 33 additions & 33 deletions docs/examples/phase-locked-loop.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/sdr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ._link_budget import *
from ._measurement import *
from ._modulation import *
from ._nco import *
from ._probability import *
from ._sequence import *
from ._signal import *
Expand Down
25 changes: 11 additions & 14 deletions src/sdr/_synchronization/_nco.py → src/sdr/_nco.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import numpy.typing as npt
from typing_extensions import Literal

from .._helper import export
from ._helper import export


@export
Expand All @@ -24,19 +24,19 @@ class NCO:
constant constant
increment offset p[n]
| | |
+----+ v v v +--------+
f[n] -->| K0 |-->@--------------+-->@-------->@-->| e^(j.) |--> y[n]
+----+ ^ | +--------+
| +------+ |
+---| z^-1 |<--+
+------+
+----+ v +------+ v v +-----------+
f[n] -->| K0 |-->@-->| z^-1 |---+-->@-------->@-->| output(.) |--> y[n]
+----+ ^ +------+ | +-----------+
| |
+--------------+
f[n] = Input frequency signal (radians/sample)
p[n] = Input phase signal (radians)
y[n] = Output complex signal
K0 = NCO gain
increment = Constant phase accumulation (radians/sample)
offset = Absolute phase offset (radians)
output(.) = Phase-to-output mapping function, either: ., sin(.), cos(.), exp(j .)
z^-1 = Unit delay
@ = Adder
Expand Down Expand Up @@ -115,13 +115,10 @@ def reset(self):
r"""
Resets the NCO.
The internal accumulator is set to $-\omega$ radians such that the phase of the first output sample
is $\theta$ radians.
Examples:
See the :ref:`phase-locked-loop` example.
"""
self._z_prev = -self.increment
self._z_prev = 0.0

@overload
def __call__(
Expand Down Expand Up @@ -179,10 +176,10 @@ def __call__(
z = freq * self.gain + self.increment
z = np.atleast_1d(z)

# Increment the first sample by the previous output. Then run a cumulative sum over all samples.
z[0] += self._z_prev
# Insert the previous output in front of the current input. Then run a cumulative sum over all samples.
z = np.insert(z, 0, self._z_prev)
z = np.cumsum(z)
self._z_prev = z[-1]
z, self._z_prev = z[0:-1], z[-1]

# Add the absolute offset to every sample
y = z + self.offset + phase
Expand Down
1 change: 0 additions & 1 deletion src/sdr/_synchronization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
"""
from ._agc import *
from ._loop_filter import *
from ._nco import *
from ._phase_error import *
from ._pll import *

0 comments on commit 4c3954c

Please sign in to comment.