Skip to content

Instantly share code, notes, and snippets.

@philpem
Created October 19, 2018 13:15
Show Gist options
  • Save philpem/09c8edc732ae435a43c34f5fb6e765d6 to your computer and use it in GitHub Desktop.
Save philpem/09c8edc732ae435a43c34f5fb6e765d6 to your computer and use it in GitHub Desktop.
Si44xx Class E matching (Jupyter notebook)
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Split TX/RX Class-E matching network per AN627 section 5.3 #\n",
"This iPython notebook implements the transmit matching procedure specified in Silicon Labs AN627.\n",
"\n",
"First we load NumPy and set up some useful constants."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"from __future__ import print_function\n",
"from enum import Enum\n",
"import numpy as np\n",
"\n",
"from ipywidgets import interact, interactive, fixed, interact_manual\n",
"import ipywidgets as widgets\n",
"\n",
"MHz=1e6\n",
"\n",
"uF=1e-6\n",
"nF=1e-9\n",
"pF=1e-12\n",
"\n",
"mH=1e-3\n",
"uH=uF\n",
"nH=nF\n",
"pH=pF\n",
"\n",
"mW=mH\n",
"\n",
"mA=mH\n",
"uA=uH\n",
"\n",
"class CHIPS(Enum):\n",
" Si4060 = 0 # Si4060/Si4460/Si4467\n",
" Si4461 = 1 # Si4461\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we define the design parameters:"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"# Transmit frequency in Hz (\"operating frequency\")\n",
"FO = 868.0*MHz\n",
"\n",
"# RF power supply voltage in Volts\n",
"VDD_RF = 3.3\n",
"\n",
"# set the chip type\n",
"CHIP = CHIPS.Si4461\n",
"\n",
"# Shunt capacitance in Farad\n",
"if CHIP == CHIPS.Si4060:\n",
" CSHUNT = 1.25*pF\n",
"elif CHIP == CHIPS.Si4461:\n",
" CSHUNT = 2.0*pF\n",
"else:\n",
" raise ValueError(\"Invalid chiptype\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5.3.1 -- Step 1: Select a value for L_Choke ##\n",
"\n",
"This is based on a straight-line fit of the data given in section 5.3.1."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"112.19959999999999"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lchoke = (-2.803e-16 * FO) + 3.555e-7\n",
"lchoke/nH"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**TODO: pick the nearest E12 value**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5.3.2 -- Step 2: Choose/calculate values for L0-C0 series-resonant tank ##\n",
"\n",
"Design guidelines:\n",
"\n",
" * The L0-C0 tank must resonate at Fo (FREQ)\n",
" * The value of L0 should be chosen to be as large as possible, while:\n",
" * Remaining low enough that self-resonance is not an issue\n",
" * Close to standard 5%-tolerance values"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Resonant frequency: 869.816 MHz\n",
"Frequency error: 0.209 %\n"
]
}
],
"source": [
"L0=9.3*nH\n",
"C0=3.6*pF\n",
"fresonant = 1./(2.*np.pi*np.sqrt(L0*C0))\n",
"print(\"Resonant frequency: %0.3f MHz\" % (fresonant/MHz))\n",
"\n",
"# calculate error vs. FO\n",
"ferror = (fresonant - FO) / FO\n",
"print(\"Frequency error: %0.3f %%\" % (ferror * 100.0))\n",
"\n",
"# impedance at resonance -- Xl and Xc = 0, therefore Z = 0 (in theory)\n",
"# in practice, Z = parasitic resistance of L and C"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5.3.3 -- Step 3: Calculate required value for Zload ##\n",
"\n",
"Calculate the required value of load impedance to be presented to the output of the resonant tank at the operating frequency (FO)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Load impedance: (27.0616565534+31.1884365599j) ohm\n"
]
}
],
"source": [
"ANG_RAD=np.deg2rad(49.0524)\n",
"zload = (0.2815/(2*np.pi*FO*CSHUNT)) * (np.cos(ANG_RAD)+(1j*np.sin(ANG_RAD)))\n",
"print(\"Load impedance: %s ohm\" % zload)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5.3.4 -- Step 4, estimate the required PA_PWR_LVL register value from the calculated value of a hypothetical voltage-limiting resistor ##"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"V_DD = 1.045 V\n",
"I_DD = 22.387 mA\n",
"R_DC = 100.718 Ohms\n"
]
}
],
"source": [
"POUT = 23.4*mW\n",
"\n",
"vdd = np.sqrt(POUT / (2.*(np.pi**2)*FO*CSHUNT))\n",
"print(\"V_DD = %0.3f V\" % (vdd))\n",
"\n",
"idd = 2.*(np.pi**2)*FO*CSHUNT*vdd\n",
"print(\"I_DD = %0.3f mA\" % (idd / mA))\n",
"\n",
"rdc = (VDD_RF - vdd) / idd\n",
"print(\"R_DC = %0.3f Ohms\" % (rdc))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we assume the PA is in switched programmable current-source mode (`PA_MODE.PA_MODE` = `SWC` = `1`). This is less efficient but provides better power flatness.\n",
"\n",
"We work out a set of `PA_BIAS_CLKDUTY.OB` and `PA_PWR_LVL.DDAC` values which would give the desired PA bias current."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OB: 17 (0x11)\n",
"DDAC: 124 (0x7C)\n",
"I: 22.32 mA\n"
]
}
],
"source": [
"# Set maximum DDAC value\n",
"if CHIP == CHIPS.Si4060:\n",
" DDAC_MAX = 0x7F # e.g. Si4461\n",
"elif CHIP == CHIPS.Si4461:\n",
" DDAC_MAX = 0x7F # e.g. Si4461\n",
"else:\n",
" raise ValueError(\"Invalid chiptype\")\n",
"\n",
"# Set maximum OB value\n",
"OB_MAX = 0x3F\n",
"\n",
"# OB = 0 = 10uA/finger, OB=1=20uA, ...\n",
"\n",
"for ob in range(0, OB_MAX):\n",
" # Calculate resulting per-finger current\n",
" i_ob = (ob + 1) * 10*uA\n",
" \n",
" # Calculate required DDAC -- round down to avoid exceeding I_DD\n",
" ddac = int(idd / i_ob)\n",
" \n",
" # Does this OB setting result in DDAC <= DDAC_MAX?\n",
" if ddac <= DDAC_MAX:\n",
" break\n",
"\n",
"# Back-calculate the actual current\n",
"iactual = ((ob + 1) * 10*uA) * ddac\n",
"\n",
" \n",
"print(\"OB: %3d (0x%02X)\" % (ob, ob))\n",
"print(\"DDAC: %3d (0x%02X)\" % (ddac, ddac))\n",
"print(\"I: %6.2f mA\" % (iactual/mA))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@philpem
Copy link
Author

philpem commented Oct 19, 2018

Useful references:

Matching network design (SiLabs knowledge base)
What is the Si4432 PA output impedance?
What are the advantages and disadvantages of switched current mode?
Class-E power vs. RDC
External PA Matching
RSSI squelching (limiting RX sensitivity)

And some about the chip:
Si4463 patch and startup delay

Alternative power amps:
Maxim MAX2601/MAX2602 (Can't find any S-parameter files)
-- MAX2602 match data for 120MHz
NXP -- ADS data but no S-params
Infineon -- S-param data available, can use with RFSim99

Dev boards:
Si4461 RF Pico Board

@philpem
Copy link
Author

philpem commented Oct 19, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment