{"id":30928081,"url":"https://github.com/mryndzionek/dpll_design_notes","last_synced_at":"2026-02-12T23:32:34.084Z","repository":{"id":294959141,"uuid":"985330456","full_name":"mryndzionek/dpll_design_notes","owner":"mryndzionek","description":"Digital PLL design notes","archived":false,"fork":false,"pushed_at":"2026-01-25T10:13:25.000Z","size":1420,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-26T01:08:48.983Z","etag":null,"topics":["control-systems","fll","laplace-transform","phase-locked-loop","pll","s-plane","sdr","transfer-function","z-plane"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mryndzionek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-17T14:36:29.000Z","updated_at":"2026-01-25T10:13:29.000Z","dependencies_parsed_at":"2025-11-22T10:00:39.645Z","dependency_job_id":null,"html_url":"https://github.com/mryndzionek/dpll_design_notes","commit_stats":null,"previous_names":["mryndzionek/dpll_design_notes"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mryndzionek/dpll_design_notes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mryndzionek%2Fdpll_design_notes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mryndzionek%2Fdpll_design_notes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mryndzionek%2Fdpll_design_notes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mryndzionek%2Fdpll_design_notes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mryndzionek","download_url":"https://codeload.github.com/mryndzionek/dpll_design_notes/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mryndzionek%2Fdpll_design_notes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29386218,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T22:07:52.078Z","status":"ssl_error","status_checked_at":"2026-02-12T22:07:49.026Z","response_time":55,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["control-systems","fll","laplace-transform","phase-locked-loop","pll","s-plane","sdr","transfer-function","z-plane"],"created_at":"2025-09-10T08:19:21.164Z","updated_at":"2026-02-12T23:32:34.079Z","avatar_url":"https://github.com/mryndzionek.png","language":"Python","readme":"# Digital PLL design notes\n\n## Introduction\n\nThere are few publications on the internet regarding analog PLL design.\nDigital PLL design articles are mostly from hardware perspective.\nVery little comprehensive material is available on \"software\" DPLL design.\nHere are some links:\n\n[Link 1](https://wirelesspi.com/phase-locked-loop-pll-in-a-software-defined-radio-sdr/)\n\n[Link 2](https://liquidsdr.org/blog/pll-howto/)\n\n[Link 3](https://www.dsprelated.com/showarticle/967.php)\n\n[Link 4](https://www.dsprelated.com/showarticle/973.php)\n\nUnfortunately, in many cases, whole parts of derivation are omitted and\nthe PLL structure assumption are undocumented. Often only the closed-loop\nresponse is shown/analyzed without providing formulas for just the loop filter.\n\nHere I'll try to do more thorough analysis of second- and third-order digital\n\"software\" PLL from the ground up. Including discretization and full formulas + plots.\nIn all cases bilinear transform is used to for discretization:\n\n$$\ns \\approx \\frac{2}{T_{s}}\\frac{1 - z^{-1}}{1 + z^{-1}};T_{s}=1\n$$\n\nIn PLL (typically) $`samplerate \\gg \\omega_n`$ and therefore frequency prewarping was not done.\n\n## Second-order digital PLL design\n\nThe following continuous-time structure is assumed:\n\n![img1](imgs/dpll_2_ct.png)\n\nThere are no gain/proportional coefficients included, as a software phase detector can output\nthe phase directly. Transfer function of the open-loop system is:\n\n$$\nH_{open}=\\frac{s \\tau_{2} + 1}{s \\tau_{1}}\\frac{1}{s}= \\frac{s \\tau_{2} + 1}{s^{2} \\tau_{1}}\n$$\n\nClosed-loop system is therefore described by:\n\n$$\nH_{closed}=\\frac{H_{open}}{1 + H_{open}}=\\frac{s \\tau_{2} + 1}{s^{2} \\tau_{1} + s \\tau_{2} + 1}=\n\\frac{\\frac{1}{\\tau_{1}} \\left(s \\tau_{2} + 1\\right)}{s^{2} + \\frac{s \\tau_{2}}{\\tau_{1}} + \\frac{1}{\\tau_{1}}}\n$$\n\nWe can identify the denominator with the standard form: $`{s^{2} + 2\\zeta\\omega_{n} + \\omega^{2}_{n}}`$\nand therefore express $`\\tau_{1}`$ and $`\\tau_{2}`$ in terms of the design parameters $`\\zeta`$ (damping factor)\nand $\\omega_{n}$ (natural frequency):\n\n$$\n\\tau_{1}=\\frac{1}{\\omega_{n}^{2}};\n\\tau_{2}=\\frac{2 \\zeta}{\\omega_{n}}\n$$\n\nUsing the bilinear transform we can discretize the closed-loop transfer function:\n\n$$\nH_{zclosed}=\\frac{\\left(z + 1\\right) \\left(2 \\tau_{2} \\left(z - 1\\right) + z + 1\\right)}{4 \\tau_{1} \\left(z - 1\\right)^{2} + 2 \\tau_{2} \\left(z - 1\\right) \\left(z + 1\\right) + \\left(z + 1\\right)^{2}}\n$$\n\nFrom this we can compute the discrete filter coefficients:\n\n$$\nb_{0}=\\frac{2 \\tau_{2} + 1}{4 \\tau_{1} + 2 \\tau_{2} + 1}, b_{1}=\\frac{2}{4 \\tau_{1} + 2 \\tau_{2} + 1}, b_{2}=\\frac{1 - 2 \\tau_{2}}{4 \\tau_{1} + 2 \\tau_{2} + 1}\n$$\n\n$$\na_{0}=1, a_{1}=\\frac{2 - 8 \\tau_{1}}{4 \\tau_{1} + 2 \\tau_{2} + 1}, a_{2}=\\frac{4 \\tau_{1} - 2 \\tau_{2} + 1}{4 \\tau_{1} + 2 \\tau_{2} + 1}\n$$\n\nSimilarly we can obtain the discrete approximation for just the loop filter:\n\n$$\nF_{z}=\\frac{\\tau_{2} \\left(z - 1\\right) + \\frac{z}{2} + \\frac{1}{2}}{\\tau_{1} \\left(z - 1\\right)}\n$$\n\nand the filter coefficients:\n\n$$\nb_{0}=\\frac{2 \\tau_{2} + 1}{2 \\tau_{1}}, b_{1}=\\frac{1 - 2 \\tau_{2}}{2 \\tau_{1}}\n$$\n$$\na_{0}=1, a_{1}-1\n$$\n\nExample:\n\n```python\nsamplerate = 1000 #Hz\nloop_bandwidth = 50 #Hz\nomega_n=2*math.pi*loop_bandwidth/samplerate # 0.31415\nzeta = 1/math.sqrt(2) # 0.707\ntau_1 = 1 / (omega_n * omega_n) # 10.1321\ntau_2 = 2 * zeta / omega_n # 4.5016\n```\n\nClosed-loop discrete filter coefficients:\n\n```python\nb = [0.19795842428558091, 0.039579165327638284, -0.15837925895794264]\na = [1.0, -1.5645039861011998, 0.6436623167564764]\n```\n\nDiscrete loop filter coefficients:\n\n```python\nb = [0.49363631582128226, -0.39494027181038893]\na = [1.0, -1.0]\n```\n\nAnd the final analysis results ($`\\zeta=0.707`$):\n\n![img2](imgs/dpll_2_plots.png)\n\n## Third-order digital PLL design\n\nThe loop structure is the same as before. The only difference is second-order loop filter:\n\n![img1](imgs/dpll_3_ct.png)\n\n$$\nF_{a}=\\frac{b \\omega_{n}^{2} s + c \\omega_{n} s^{2} + \\omega_{n}^{3}}{s^{2}}\n$$\n\nOpen-loop transfer function is:\n\n$$\nH_{open}=\\frac{b \\omega_{n}^{2} s + c \\omega_{n} s^{2} + \\omega_{n}^{3}}{s^{2}}\\frac{1}{s}=\\frac{\\omega_{n} \\left(b \\omega_{n} s + c s^{2} + \\omega_{n}^{2}\\right)}{s^{3}}\n$$\n\nClosed-loop system is therefore described by:\n\n$$\nH_{closed}=\\frac{H_{open}}{1 + H_{open}}=\n\\frac{\\omega_{n} \\left(b \\omega_{n} s + c s^{2} + \\omega_{n}^{2}\\right)}{b \\omega_{n}^{2} s + c \\omega_{n} s^{2} + \\omega_{n}^{3} + s^{3}}\n$$\n\nWe can find formulas for the poles of the system (zeros of the denominator). There is one purely real pole:\n\n$$\np_{1} = \\omega_{n} \\left(- \\frac{\\sqrt[3]{\\alpha}}{3} - \\frac{c}{3} - \\frac{- 3 b + c^{2}}{3 \\sqrt[3]{\\alpha}}\\right)\n$$\n\nand a conjugate pair of poles:\n\n$$\np_{2} = \\omega_{n} \\left(- \\frac{\\sqrt[3]{\\alpha} \\left(- \\frac{1}{2} \\pm \\frac{\\sqrt{3} \\mathrm{j}}{2}\\right)}{3} - \\frac{c}{3} - \\frac{- 3 b + c^{2}}{3 \\sqrt[3]{\\alpha} \\left(- \\frac{1}{2} \\pm \\frac{\\sqrt{3} \\mathrm{j}}{2}\\right)}\\right)\n$$\n\nwhere:\n\n$$\n\\alpha=-\\frac{9 b c}{2} + c^{3} + \\frac{\\sqrt{- 4 \\left(- 3 b + c^{2}\\right)^{3} + \\left(- 9 b c + 2 c^{3} + 27\\right)^{2}}}{2} + \\frac{27}{2}\n$$\n\nFinding the design parameters $`b`$ and $`c`$ is not easy in this case, but having the formulas for the poles we can find the parameters numerically.\nWe can focus on the complex pole and try to minimize the distance between this pole and a desired pole $`\\omega_ne^{\\mathrm{j}(\\pi - \\cos^{-1}\\zeta)}`$.\nWe can see from the formulas that $`\\omega_n`$ is just a scaling factor, so $`b`$ and $`c`$ effectively are determined by the damping factor $`\\zeta`$.\nHere are values for some choices of $`\\zeta`$:\n\n| $`\\zeta`$  |   $`b`$  |   $`c`$  |\n| ---------- | -------- | -------- |\n|    0.0     | 1.0      | 1.0      |\n|    0.4     | 1.8      | 1.8      |\n|    0.5     | 2        | 2        |\n| 0.707 ($`\\frac{1}{\\sqrt{2}}`$)  | 2.414213 | 2.414213 |\n|    0.8     | 2.6      | 2.6      |\n|    0.9     | 2.8      | 2.8      |\n\nSo it looks that for $`\\zeta`$ in range $`[0,0.9]`$, $`b=c=1 + 2\\zeta`$.\n\nAnother design parameter selection scheme, offering better closed-loop response in\nsome cases (the real pole is closer to zero), might be:\n\n1. Set $`b`$ to $`2.9999`$\n2. Choose $`c`$ from below table for desired $`\\zeta`$\n3. Multiply the desired $`\\omega_{n}`$ by $`\\alpha`$\n\n| $`\\zeta`$  |   $`c`$  |  $`\\alpha`$  |\n| ---------- | -------- | ------------ |\n|    0.0     |  0.3333  |   0.5774     |\n|    0.1     |  0.6865  |   0.589      |\n|    0.2     |  1.0269  |   0.602      |\n|    0.3     |  1.3533  |   0.6166     |\n|    0.4     |  1.6643  |   0.6333     |\n|    0.5     |  1.9581  |   0.6527     |\n|    0.6     |  2.2322  |   0.6759     |\n|    0.7     |  2.4831  |   0.7048     |\n|    0.8     |  2.7053  |   0.7431     |\n|    0.9     |  3.1927  |   1.3711     |\n\nNow we know roughly how choose design parameters, so next step is discretization.\nHere is the discretized transfer function:\n\n$$\nH_{z}=\\frac{\\omega_{n} \\left(- 2 b \\omega_{n} + 4 c + \\omega_{n}^{2} + z^{3} \\left(2 b \\omega_{n} + 4 c + \\omega_{n}^{2}\\right) + z^{2} \\left(2 b \\omega_{n} - 4 c + 3 \\omega_{n}^{2}\\right) - z \\left(2 b \\omega_{n} + 4 c - 3 \\omega_{n}^{2}\\right)\\right)}\n{\\left(z^{3} + \\frac{z^{2} \\left(2 b \\omega_{n}^{2} - 4 c \\omega_{n} + 3 \\omega_{n}^{3} - 24\\right)}{2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} + 8} + \\frac{z \\left(- 2 b \\omega_{n}^{2} - 4 c \\omega_{n} + 3 \\omega_{n}^{3} + 24\\right)}{2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} + 8} + \\frac{- 2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} - 8}{2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} + 8}\\right) \\left(2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} + 8\\right)}\n$$\n\nand the filter coefficients:\n\n$`b_{0}`$ to $`b_{3}`$\n\n$$\n\\left( \\frac{\\alpha - 8}{\\alpha}, \\  \\frac{2 b \\omega_{n}^{2} - 4 c \\omega_{n} + 3 \\omega_{n}^{3}}{\\alpha}, \\  \\frac{- 2 b \\omega_{n}^{2} - 4 c \\omega_{n} + 3 \\omega_{n}^{3}}{\\alpha}, \\  \\frac{- 2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3}}{\\alpha}\\right)\n$$\n\n$`a_{0}`$ to $`a_{3}`$\n\n$$\n\\left( 1, \\  \\frac{2 b \\omega_{n}^{2} - 4 c \\omega_{n} + 3 \\omega_{n}^{3} - 24}{\\alpha}, \\  \\frac{- 2 b \\omega_{n}^{2} - 4 c \\omega_{n} + 3 \\omega_{n}^{3} + 24}{\\alpha}, \\  \\frac{- 2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} - 8}{\\alpha}\\right)\n$$\n\nwhere:\n\n$$\n\\alpha = 2 b \\omega_{n}^{2} + 4 c \\omega_{n} + \\omega_{n}^{3} + 8\n$$\n\nNext the discrete approximation for just the loop filter:\n\n$$\n\\frac{\\omega_{n} \\left(2 b \\omega_{n} \\left(z - 1\\right) \\left(z + 1\\right) + 4 c \\left(z - 1\\right)^{2} + \\omega_{n}^{2} \\left(z + 1\\right)^{2}\\right)}{4 \\left(z - 1\\right)^{2}}\n$$\n\nand the loop filter coefficients:\n$`b_{0}`$ to $`b_{2}`$\n\n$$\n\\left( \\frac{b \\omega_{n}^{2}}{2} + c \\omega_{n} + \\frac{\\omega_{n}^{3}}{4}, \\  - 2 c \\omega_{n} + \\frac{\\omega_{n}^{3}}{2}, \\  - \\frac{b \\omega_{n}^{2}}{2} + c \\omega_{n} + \\frac{\\omega_{n}^{3}}{4}\\right)\n$$\n\n$`a_{0}`$ to $`a_{2}`$\n\n$$\n\\left( 1, \\  -2, \\  1\\right)\n$$\n\nExample:\n\n```python\nsamplerate = 1000 #Hz\nloop_bandwidth = 50 #Hz\nomega_n=2*math.pi*loop_bandwidth/samplerate # 0.31415\nzeta = 1/math.sqrt(2) # 0.707\nb = 1 + 2 * zeta # 2.4142\nc = b\n```\n\nHere is a pole-zero map in s-plane:\n\n![img3](imgs/dpll_3_pole_zero_plot.png)\n\nClosed-loop discrete filter coefficients:\n\n```python\nb = [0.30683977743424357, -0.21351282207666347, -0.2960936186119176, 0.2242589808989895]\na = [1.0, -2.2929934897739326, 1.7833870490853516, -0.4689012416667669]\n```\n\nDiscrete loop filter coefficients:\n\n```python\nb = [0.8853357923467264, -1.501391980009482, 0.6470624643430553]\na = [1.0, -2.0, 1.0]\n```\n\nHere is a pole-zero map in z-plane:\n\n![img4](imgs/dpll_3_pole_zero_plot_z.png)\n\n![img5](imgs/dpll_3_pole_zero_zoom_plot_z.png)\n\nSimulations show that values chosen for $`b`$ and $`c`$ are a good starting point:\n\n$`b=c=2.4142`$\n![img6](imgs/dpll_3_plots_1.png)\n\nHere are also results for $`b=c=2.8`$\n\n$`b=c=2.8`$\n![img7](imgs/dpll_3_plots_2.png)\n\nZooming into the PLL simulation output shows that 3rd-order PLL can track frequency changes -\nthere is no lag in presence of constant input frequency change - and the lock is achieved sooner:\n\n![img8](imgs/dpll_phase_err.png)\n\n## Python design script\n\n[dpll.py](dpll.py) is a simple Python script containing all the design procedures described in this\ndocument.\n\nHere are also some more DPLL outputs to a AM modulated signal with linearly varying frequency and\nsome frequency jumps:\n\nInitial lock of DPLL order 3:\n\n![img9](imgs/dpll_3_initial_lock.png)\n\nResponse to an abrupt frequency change:\n\n![img10](imgs/dpll_3_abrupt_freq.png)\n\nComparison of responses for different orders and alternative design:\n\n![img11](imgs/dpll_err_resp.png)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmryndzionek%2Fdpll_design_notes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmryndzionek%2Fdpll_design_notes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmryndzionek%2Fdpll_design_notes/lists"}