{
"cells": [
{
"cell_type": "markdown",
"id": "2976371b-c4ca-4346-8e5e-341d5b0a031a",
"metadata": {},
"source": [
"## TX output impedance\n",
"\n",
"In a series of Fediverse toots starting [here](https://astrodon.social/@quantensalat/111654942242850354), Alexander Knochel DK3HD measured the output impedance of his Xiegu X6100 at 1 W output.\n",
"\n",
"As a measurement device, he used a spectrum analyzer, which made possible some rather ingenious measurement approach.\n",
"\n",
"The measurement setup is a resistive divider of a 1000 Ω resistor `R1` in series with a 47 Ω `R2`. There is a signal generator of unknown source impedance `Rs` that drives that divider. Alexander adds his TX in parallel to `R2`, which doubles as a dummy load. The signal generator and the TX operate at nearby, but different frequencies: 14.05 MHz the TX, 14 MHz the signal generator.\n",
"\n",
"The measurement itself is done via a spectrum analyzer with input impedance of 50 Ω. That spectrum analyzer is used in series with a 10 kΩ resistor, leading to a total spectrum analyzer input impedance `Ra` of 10,050 Ω.\n",
"\n",
"Three measurement runs were done. In each measurement run, the spectrum analyzer was used to measure `Ua` parallel to the signal generator and `Ub` parallel to the TX.\n",
"\n",
"Actually, the raw data as made available by the spectrum analyzer is in power, in dBm, so, rather, `Pa` and `Pb` are measured, from which `Ua` and `Ub` can be determined.\n",
"\n",
"* In [measurement 1](https://astrodon.social/@quantensalat/111654978724862031), the TX was present and switched to SSB, but no audio frequency input was present, so the TX did not generate a signal of its own. Results: `Pa=-45.6 dBm, Pb=-80.1 dBm`.\n",
"* In [measurement 2](https://astrodon.social/@quantensalat/111654990868516067), the TX was completely removed, so the spectrum analyzer just measured the signal generator's output before and after attenuation via the resistive divider `R1` / `R2`. Results: `Pa=-45.1 dBm, Pb=-71.6 dBm`.\n",
"* In [measurement 3](https://astrodon.social/@quantensalat/111655003856290660), the TX was reconnected and set to AM to produce a 1 W signal. The analyzer was used to determine the signal at the signal generator's frequency, not the (nearby) TX frequency. Results: `Pa=-25.85 dBm, Pb=-59.7 dBm`.\n",
"* There apparaently was a [measurement 4](https://astrodon.social/@quantensalat/111657012393091052) with a TX signal of 4 W, but I did not find the raw data of that one; probably, Alexander has not published it.\n",
"\n",
"In his analysis of these measurements, Alexander determined the TX impendance for the spectrum analyzer's signal to be 32.5 Ω from measurement 1 and 36.1 Ω from measurement 3. Just using the same calculation for measurement 2 should yield infinite impedance, as the TX was disconnected, but actually did yield -881 Ω. Measurement 4 produced 40.8 Ω.\n",
"\n",
"His simplified analysis neglected `Ra`, treating it as high enough to ignore, and also treated `Rs` as low. In my more pedantic analysis that follows (as Python code), I use measurement 2 to estimate `Rs` (it comes out to 1360 Ω) and also take `Ra` into account. With that analysis, I get 29.6 Ω from measurement 1 and 33.6 Ω from measurement 3."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3f9b7338-fe9c-4396-b020-071cdec209ca",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Measurement 2:\n",
"Estimated signal generator source voltage: 0.608 V\n",
"Voltage quotient seen: 21.135 = 1/0.047315\n",
"Quotient high rs: 20.271, low rs: 22.376\n",
"rs determined: 1359.5, quot is 21.135\n",
"\n",
"Measurement 1:\n",
"Impendance of the tx is: 29.6 Ω\n",
"\n",
"Measurement 3:\n",
"Impendance of the tx is: 33.6 Ω\n"
]
},
{
"data": {
"text/plain": [
"(29.597606793504738, 33.625650469031996)"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import math\n",
"import scipy.optimize\n",
"\n",
"def analyze_the_measurements(\n",
" r1, r2, ra,\n",
" pa1, pb1,\n",
" pa2, pb2,\n",
" pa3, pb3,\n",
" trace):\n",
"\n",
" LOG10_of_50 = math.log(50) / math.log(10)\n",
"\n",
" def voltage_from_dbm(p):\n",
" # Voltage, in Volt, across a 50 Ω load, from power, in dBm.\n",
" return 10 ** (0.5 * (p/10 - 3 + LOG10_of_50))\n",
"\n",
" def parallel(rx, ry):\n",
" # Parallel of two impendances.\n",
" if rx == math.inf:\n",
" return ry\n",
" elif ry == math.inf:\n",
" return rx\n",
" else:\n",
" return rx * ry / (rx + ry)\n",
"\n",
" def source_voltage_of_signal_generator(rs, rtx, pa):\n",
" # Given an estimate of rs and rtx and an pa value,\n",
" # estimate the source voltage of the signal generator\n",
" # (it would exhibit into an open load).\n",
" voltage_at_analyzer = voltage_from_dbm(pa)\n",
" voltage_at_measurement_point = voltage_at_analyzer / 50 * ra\n",
" resistor_at_analyzer = parallel(ra, r1 + parallel(r2, rtx))\n",
" signal_generator_load_current = voltage_at_measurement_point / resistor_at_analyzer\n",
" return signal_generator_load_current * (rs + resistor_at_analyzer)\n",
"\n",
" def estimated_ua(usig, rs, rtx):\n",
" combined_load_at_r2 = parallel(r2, rtx)\n",
" combined_load_at_r1 = parallel(ra, combined_load_at_r2 + r1)\n",
" u_at_r1 = usig / (combined_load_at_r1 + rs) * combined_load_at_r1\n",
" return u_at_r1 / ra * 50\n",
"\n",
" def estimated_ub(usig, rs, rtx):\n",
" # Given an estimate for usig, rs, and rtx,\n",
" # calculate the signal the analyzer should see, in volts.\n",
" combined_load_sa_and_tx = parallel(ra, parallel(r2, rtx))\n",
" signal_across_rtx = usig / (combined_load_sa_and_tx + r1 + rs) * combined_load_sa_and_tx\n",
" return signal_across_rtx / ra * 50\n",
"\n",
" def voltage_quotient_seen(pa, pb):\n",
" return voltage_from_dbm(pa) / voltage_from_dbm(pb)\n",
"\n",
" # Use the data from measurement 2 to estimate rs:\n",
" uquot_meas_2 = voltage_quotient_seen(pa2, pb2)\n",
" def uqot_calculated(rs, rtx):\n",
" usig = source_voltage_of_signal_generator(rs, rtx, pa2)\n",
" return estimated_ua(usig, rs, rtx) / estimated_ub(usig, rs, rtx)\n",
"\n",
" rs_low = 0\n",
" rs_high = 1e6\n",
" \n",
" uquot_low = uqot_calculated(rs_low, math.inf)\n",
" uquot_high = uqot_calculated(rs_high, math.inf)\n",
"\n",
" assert uquot_high < uquot_meas_2 < uquot_low\n",
"\n",
" sol_m2 = scipy.optimize.root_scalar(\n",
" f=lambda rs: uqot_calculated(rs, math.inf)-uquot_meas_2,\n",
" bracket=(rs_low, rs_high),\n",
" method=\"brentq\"\n",
" )\n",
" assert(sol_m2.converged)\n",
" rs = sol_m2.root\n",
" \n",
" if trace:\n",
" # print(f\"U_signal_generator, simple analysis: {usig:.3} V\")\n",
" print(\"Measurement 2:\")\n",
" print(f\"Estimated signal generator source voltage: {source_voltage_of_signal_generator(rs, math.inf, pa2):.3} V\")\n",
" print(f\"Voltage quotient seen: {uquot_meas_2:.5} = 1/{1/uquot_meas_2:.5}\")\n",
" print(f\"Quotient high rs: {uquot_high:.5}, low rs: {uquot_low:.5}\")\n",
" print(f\"rs determined: {rs:.5}, quot is {uqot_calculated(rs, math.inf):.5}\")\n",
"\n",
" # Now, calculate rtx from measurement 1\n",
" uquot_meas_1 = voltage_quotient_seen(pa1, pb1)\n",
" sol_mes1 = scipy.optimize.root_scalar(\n",
" f=lambda rtx: uqot_calculated(rs, rtx)-uquot_meas_1,\n",
" bracket=(1e-2, 1e5),\n",
" method=\"brentq\"\n",
" )\n",
" assert(sol_mes1.converged)\n",
" rtx_1 = sol_mes1.root\n",
" if trace:\n",
" print(\"\\nMeasurement 1:\")\n",
" print(f\"Impendance of the tx is: {rtx_1:.3} Ω\")\n",
"\n",
" # And that from measurement 3\n",
" uquot_meas_3 = voltage_quotient_seen(pa3, pb3)\n",
" sol_mes3 = scipy.optimize.root_scalar(\n",
" f=lambda rtx: uqot_calculated(rs, rtx)-uquot_meas_3,\n",
" bracket=(1e-2, 1e5),\n",
" method=\"brentq\"\n",
" )\n",
" assert(sol_mes3.converged)\n",
" rtx_3 = sol_mes3.root\n",
" if trace:\n",
" print(\"\\nMeasurement 3:\")\n",
" print(f\"Impendance of the tx is: {rtx_3:.3} Ω\")\n",
" return (rtx_1, rtx_3)\n",
"\n",
"\n",
"analyze_the_measurements(\n",
" r1=1000, r2=47, ra=10050,\n",
" pa1=-45.6, pb1=-80.1,\n",
" pa2=-45.1, pb2=-71.6,\n",
" pa3=-25.85, pb3=-59.7,\n",
" trace=True)"
]
},
{
"cell_type": "markdown",
"id": "43bfe39c-eec0-4040-8328-6f0209bd3eae",
"metadata": {},
"source": [
"## How to reproduce?\n",
"\n",
"* Install [Python](https://www.python.org/)\n",
"* Start a [`venv`](https://docs.python.org/3/library/venv.html)\n",
"* Activate that `venv`\n",
"* Run `pip install jupyter notebook scipy`\n",
"* Download `TX_Impedance_DK3HD.ipynb`, e.g., from [https://dj3ei.famsik.de/2023-TX_Impedance/TX_Impedance_DK3HD.ipynb](https://dj3ei.famsik.de/2023-TX_Impedance/TX_Impedance_DK3HD.ipynb).\n",
"* Run `jupyter lab`\n",
"* Wait for your browser to open, navigate to `TX_Impedance_DK3HD.ipynb`, and open."
]
},
{
"cell_type": "markdown",
"id": "faca7a0b-8019-4271-bdfe-4cc15b458126",
"metadata": {},
"source": [
"## License (MIT)\n",
"\n",
"Copyright 2023 Dr. Andreas Krüger, DJ3EI.\n",
"\n",
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n",
"\n",
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n",
"\n",
"THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}