```python
import math
import bisect

import pint
import matplotlib.pyplot as plt
import numpy as np
import sympy

ureg = pint.UnitRegistry()
ureg.default_format = '.2f'

## Solves an expression for any variable within it
## Evaluates the expression (in the current context) with a unit coversion
def solve(exp, variable, unit):
    # Convert from string expression to symbol one    
    # Solve the equaltion for the desired variable and convert to string
    # Evaluate the expression string with the desired unit conversion
    exp = sympy.sympify(exp)
    exp = sympy.sstr(sympy.solve(exp, variable)[0])
    return eval("(" + exp + ").to(ureg." + unit + ")")

## Values for each decade of 1% resistors
RESISTOR_DECADE = [
    10.0, 10.2, 10.5, 10.7, 11.0, 11.3, 11.5, 11.8, 12.1, 12.4, 12.7, 13.0, 13.3, 13.7, 14.0, 14.3, 
    14.7, 15.0, 15.4, 15.8, 16.2, 16.5, 16.9, 17.4, 17.8, 18.2, 18.7, 19.1, 19.6, 20.0, 20.5, 21.0,
    21.5, 22.1, 22.6, 23.2, 23.7, 24.3, 24.9, 25.5, 26.1, 26.7, 27.4, 28.0, 28.7, 29.4, 30.1, 30.9,
    31.6, 32.4, 33.2, 34.0, 34.8, 35.7, 36.5, 37.4, 38.3, 39.2, 40.2, 41.2, 42.2, 43.2, 44.2, 45.2,
    46.4, 47.5, 48.7, 49.9, 51.1, 52.3, 53.6, 54.9, 56.2, 57.6, 59.0, 60.4, 61.9, 63.4, 64.9, 66.5,
    68.1, 69.8, 71.5, 73.2, 75.0, 76.8, 78.7, 80.6, 82.5, 84.5, 86.6, 88.7, 90.9, 93.1, 95.3, 97.6
]

## Generate a list of all 1% resistor values
RESISTOR_DECADES = [1, 10, 100, 1000, 10000, 100000, 1000000]
RESISTOR_VALUES  = sum([[val*idx for val in RESISTOR_DECADE] for idx in RESISTOR_DECADES], [])

## Binary search through resistor values for the nearest value
def select_resistor(value):
    idx = bisect.bisect_left(RESISTOR_VALUES, value.to(ureg.Ω).magnitude)
    return RESISTOR_VALUES[idx]
```

## USB Hub System Definitions


```python
Vinmax = 24 * ureg.V
Vinmin = 12 * ureg.V
Vout = 5 * ureg.V
Ilim = 5.95 * ureg.A

## Resistor divider determines operating frequency
## Rup is between FREQ and VIN, Rdn is to GND
## If FREQ connected to VIN (without a pull down resistor) then fsw will be 600 kHz

# Rup = 100 * ureg.kΩ # Recommended value from datasheet
# Rdn = 1000 * ureg.kΩ
Rdn = False
```

## MIC45208 Device Properties

#### Switching Frequency




```python
## Calculate switching frequency from Rup & Rdn
if Rdn == False:
    fsw = 600 * ureg.kHz
else:
    fsw = 600 * ureg.kHz * Rdn / (Rup + Rdn)

## Device Properties (from datasheet)
L     = 1.0 * ureg.uH    # Inductor is integrated into the package
Rdson = 16 * ureg.mΩ     # On-resistance of low-side power MOSFET
Icl   = 70 * ureg.uA     # Current-limit source current
Vcloffset = 14 * ureg.mV # Current-limit threshold
Toffmin = 200 * ureg.ns  # Minimum off time
```

## Calculations of duty cycle @ different operating voltages


```python
Ton = lambda Vop: (Vout / (Vop*fsw)).to(ureg.ns)
Tonmin = Ton(Vinmax)
Tonmax = Ton(Vinmin)
Ts     = (1/fsw).to(ureg.ns)
Dmax   = (Ts - Toffmin) / Ts

print("FSW       :", fsw.to(ureg.kHz))
print("Duty(max) :", Dmax)
print("Toff(min) :", Toffmin.to(ureg.ns))

for Vop in [Vinmin/2, Vinmin, Vinmax]:
    Toff = Ts - Ton(Vop)
    D = Ton(Vop) / Ts
    print()
    print("-- VIN", Vop, "--")
    print("   Ton  :", Ton(Vop))
    print("   Toff :", Toff)
    print("   Duty :", D)
    
    if Toff < Toffmin*1.25:
        print("   WARN : Toff close to or lower than Toffmin")
        
    if D > min(0.85, Dmax):
        print("   WARN : Tudy cycle exceeds specification max")
```

    FSW       : 600.00 kilohertz
    Duty(max) : 0.88 dimensionless
    Toff(min) : 200.00 nanosecond
    
    -- VIN 6.00 volt --
       Ton  : 1388.89 nanosecond
       Toff : 277.78 nanosecond
       Duty : 0.83 dimensionless
    
    -- VIN 12.00 volt --
       Ton  : 694.44 nanosecond
       Toff : 972.22 nanosecond
       Duty : 0.42 dimensionless
    
    -- VIN 24.00 volt --
       Ton  : 347.22 nanosecond
       Toff : 1319.44 nanosecond
       Duty : 0.21 dimensionless


## Component Selection
### Current Limit Resistor

Based on desired current limit and other operating points (inductor peak to peak current, MOSFET resistance, etc) the current limiting resistor between SW and ILIM is determined.

The MOSFET RDS(ON) varies 30% to 40% with temperature; therefore, it is recommended to add a 50% margin to ILIM to avoid false current limiting due to increased MOSFET junction temperature rise.  After finding the ideal Rlimit values, we contrain it to actual resistor values and calculate the resutling current limit.


```python
ILpp = Vout * (Vinmax - Vout) / (Vinmax * fsw * L)
print("Inductor peak to peak :", ILpp.to(ureg.A))

exp = "((Ilim*1.5 - ILpp/2.0)*Rdson + Vcloffset)/Icl - Rlimit"

Rlimitideal = solve(exp, variable='Rlimit', unit='Ω')
Rlimit = (select_resistor(Rlimitideal) * ureg.Ω).to(ureg.kΩ)

if Rlimit != Rlimitideal:
    print("Rlimit : changing from ", Rlimitideal.to(ureg.Ω), "to", Rlimit.to(ureg.Ω))
else:
    print("Rlimit :", Rlimit)
    
Ilimactual = solve(exp, variable='Ilim', unit='A')
print("Ilimit :", Ilimactual)

# Climit value copied from EVM design guide
Climit = 15 * ureg.pF
tc = Rlimit * Climit
print("Ilimit TC :", tc.to(ureg.ns))

if tc > Toffmin * 0.2:
    print("WARN : Current limit time constant too close to minimum off time")
```

    Inductor peak to peak : 6.60 ampere
    Rlimit : changing from  1486.03 ohm to 1500.00 ohm
    Ilimit : 5.99 ampere
    Ilimit TC : 22.50 nanosecond


### Input Capacitor Selection


```python
## Build dict of [Value, ESR, Impedance] of caps at 12v
CAPS = {}
CAPS["0805_1u0"] = [ 1.0*ureg.uF, 18.6, 288.0] ## C0805C105K5RAC
CAPS["1206_4u7"] = [ 4.7*ureg.uF, 13.1,  62.3] ## C1206C475K5PAC
CAPS["2917_33u"] = [33.0*ureg.uF, 29.3,  29.8] ## T521D336M035ATE065

Cins = ["2917_33u"]
# Cins = ["1206_4u7", "1206_4u7", "0805_1u0"] + ["2917_33u", "2917_33u"]

f = fsw

Zsum = 0
Ceff = 0

for key in Cins:
    cap = CAPS[key]
    
    Ceff += cap[0]
    esr   = cap[1]*ureg.mΩ
    imp   = cap[2]*ureg.mΩ * -1j
    
    ## Impedance for an idea cap. This is not accurate as bias voltage increases.
    ## Therefore, we use ESR from the SKU datasheets at 12v (listed above). 
    # imp = (-1j / (2*math.pi*f*cap[0])).to(ureg.mΩ)
    
    Zsum += 1/(esr + imp)    

ESRCin = (1 / Zsum).real
Cinsum = Ceff.to(ureg.uF)

print("Cinsum:", Cinsum)

## Current increases with duty cycle, so calculate at VIN = 12
## Load on cap increases as duty cycle drops, so calculate at VIN = 24
for vin in [Vinmin, Vinmax]:
    D = Ton(vin) / Ts
    Icinrms = Ilim * math.sqrt(D*(1-D))

    ## Inductor current changes as vin does, so we need to recalculate it
    ILpp = Vout * (vin - Vout) / (vin * fsw * L)
    
    ## These require knowing the ESR of the input caps
    dVin = ILpp * ESRCin
    Cinreq = Ilim * (1-D) / (fsw * dVin)
    Pcinrms = Icinrms**2 * ESRCin

    print()
    print("Vin @ {}".format(vin))
    print("Cin ")
    print("  dV    :", dVin.to(ureg.mV))
    print("  req   :", Cinreq.to(ureg.uF))
    print("  I rms :", Icinrms)
    print("  P rms :", Pcinrms.to(ureg.W))

```

    Cinsum: 33.00 microfarad
    
    Vin @ 12.00 volt
    Cin 
      dV    : 142.43 millivolt
      req   : 40.61 microfarad
      I rms : 2.93 ampere
      P rms : 0.25 watt
    
    Vin @ 24.00 volt
    Cin 
      dV    : 193.30 millivolt
      req   : 40.61 microfarad
      I rms : 2.42 ampere
      P rms : 0.17 watt


### Output Voltage Setting Components

A typical value of RFB1 used on the standard evaluation board is 10 kΩ. If R1 is too large, it may allow noise to be introduced into the voltage feedback loop. If RFB1 is too small in value, it will decrease the efficiency of the power supply, especially at light loads. Once RFB1 is selected, RFB2 can be calculated.


```python
Vfb  = 0.8 * ureg.V
RFB1 = 10 * ureg.kΩ

# Boost 5v output a bit to compenstate for loss in downstream cabling
Vout = Vout * 1.02

RFB2ideal = Vfb * RFB1 / (Vout - Vfb)
RFB2 = (select_resistor(RFB2ideal) * ureg.Ω).to(ureg.kΩ)

if RFB2ideal != RFB2:
    print("RFB2 : changing from ", RFB2ideal.to(ureg.Ω), "to", RFB2.to(ureg.Ω))
else:
    print("RFB2 :", RFB2)
    
Voutactual = Vfb * (1+ RFB1/RFB2)
print("Vout : changing from ", Vout, "to", Voutactual)
```

    RFB2 : changing from  1860.47 ohm to 1870.00 ohm
    Vout : changing from  5.10 volt to 5.08 volt


152eaec907a7404d3898cc710139bb59bc0354be
ipynb
Jupyter Notebook
USBHub_DC_Regulator.ipynb
CapableRobot/notebooks
d051bd4473c617f4c4cd3462c16800a285323add
MIT
2019-06-24T17:18:10.000Z
2022-01-20T15:32:15.000Z
USBHub_DC_Regulator.ipynb
CapableRobot/notebooks
d051bd4473c617f4c4cd3462c16800a285323add
MIT
USBHub_DC_Regulator.ipynb
CapableRobot/notebooks
d051bd4473c617f4c4cd3462c16800a285323add
MIT
2019-06-25T15:20:25.000Z
2019-08-17T14:53:03.000Z
Qwen/Qwen-72B
1. YES
2. YES