This post describes how I use Python code to apply Ohms Law to a resistor network to help select optimum component values that don't exceed the operating limits of an IC.
In order to determine if the idea I had for controlling a LM317 regulator with a digital potentiometer was workable I had to determine that the limits of the MC4261 digipot would not be exceeded. In this case that means no more than 2.5mA flowing through the digipot resistor and a voltage drop across the resistor of no more than 5V (the supply voltage of the chip).
This meant applying Ohms law and calculating the voltage and current across individual resistors in the network. I tend to use mostly digital components so about the only time I apply Ohms law is to calculate the value of a current limiting resistor for LED circuits or the resistor values I need for a voltage divider. I needed to hit the books and refresh my memory so I could apply it correctly.
The first part of this post is a quick introduction to Ohms law and how to apply it to resistor networks - if you already know this you can skip to the bottom. Along with the descriptions I am also providing some useful Python functions to help you with the calculations involved so it might well be worth reading anyway.
Ohm's Law describes the mathematical relationship between voltage, current and resistance in a circuit. The most common representation is the triangle shown below.
If you cover the value you want to calculate you are left with the two values you need to have to determine it as well as the formula you need to apply. So to calculate volts for example you need to multiply the resistance (R) and current (I).
Remember that the current is always expressed in Amps, if you are using small currents, like 20mA for a LED for example, you express it as 0.02A for the calculations.
Applying Ohms law to resistor networks requires a little bit more thought. Any combination of resistors can be represented as a single effective resistance value that you can plug into the equation to give you the total voltage or current running through the network, calculating the current or voltage across an individual resistor in the network requires a bit more work.
When resistors are combined in series the total resistance is simply the sum of the individual resistor values. Because there is only one path for current to flow the current though each resistor is the same and can be calculated using the total resistance value.
The voltage drop over each resistor is different though, each resistor has a voltage drop that is proportional to the total resistance. The formula in the diagram above for VRn uses the current value, it can be simplified to VRn = V * (Rn / Rt). Here is a set of Python functions to help with calculations involving resistors in series:
def rseries(*args): """ Calculate the effective resistance for a set of resistors in series. Returns a single value that represents the effective total resistance """ return float(sum(args)) def iseries(v, *args): """ Calculate the current across a set of resistors in series given the voltage across the network. Returns a list of values representing the current for each resistor in the network. """ return tuple([ v / rseries(*args) ] * len(args)) def vseries(v, *args): """ Calculate the voltage across individual resistors in series given the voltage across the network. Returns a list of values representing the voltage for each resistor in the network. """ rt = rseries(*args) return tuple([ v * r / rt for r in args ]) print rseries(10, 20, 30) # 60.0 print iseries(5, 10, 20, 30) # (0.08333333333333333, 0.08333333333333333, 0.08333333333333333) print vseries(5, 10, 20, 30) # (0.8333333333333334, 1.6666666666666667, 2.5)
If you are not familiar with Python the '*args' construct captures all remaining arguments to the function and allows you to access them as a single list called 'args'. This allows for a variable number of arguments, useful in cases like this where we want to perform the calculations on an arbitrary number of resistors.
When resistors are placed in parallel the situation is reversed - the voltage drop across each resistor is the same but, because there are multiple paths the current can flow through, the current through each resistor is different. Here the current through an individual resistor Rn is the voltage divided by the value of the resistor.
Calculating the total effective resistance is different as well - it is the inverse of the sum of the inverse of each individual resistor as shown in the diagram. When there are only two resistors (R1 and R2) this can be simplified to Rt = (R1 * R2) / (R1 + R2).
The corresponding Python functions are shown below:
def rparallel(*args): """ Calculate the effective resistance for a set of resistors in parallel. Returns a single value that represents the effective total resistance """ return 1.0 / sum([ 1 / float(r) for r in args ]) def iparallel(v, *args): """ Calculate the current across a set of resistors in parallel given the voltage across the network. Returns a list of values representing the current for each resistor in the network. """ return tuple([ v / float(r) for r in args ]) def vparallel(v, *args): """ Calculate the voltage across individual resistors in parallel given the voltage across the network. Returns a list of values representing the voltage for each resistor in the network. """ return tuple([ v ] * len(args)) print rparallel(10, 20, 30) # 5.45454545455 print iparallel(5, 10, 20, 30) # (0.5, 0.25, 0.16666666666666666) print vparallel(5, 10, 20, 30) # (5, 5, 5)
Now that I have some Python code in place to help with the calculations I can determine the 'best' resistor values that meet the requirements for the regulator circuit.
The LM317 Circuit
The circuit I am hoping to use looks like the diagram below:
The LM317 will adjust the output voltage so that the voltage drop across R1 is always 1.25V giving an output voltage that is equal to 1.25 * (1 + R2/R1). In my design the R1/R2 network is a little more complicated as shown in the diagram below.
In this case the value of R2 is the total effective resistance of two resistors in parallel - R2A and R2B. The R2B value is the total effective resistance of two resistors in series - R2B1 and R2B2 (the digipot itself).
What I need to determine is the current and voltage drop over R2B2 for a specific set of resistor values. We also need to do the calculations twice - once with the digipot at it's minimum value and again at it's maximum range. The
relevant formulas are shown in the diagram, I'm going to use the Python functions described above to make the calculations a bit more readable:
def digipot(r1, r2a, r2b1, r2b2): """ Calculate the voltage drop and current passing through the digipot (r2b2) """ # Calculate the 'virtual' resistor values r2b = rseries(r2b1, r2b2) r2 = rparallel(r2a, r2b) # Calculate the output voltage now we have R1 and R2 vout = 1.25 * (1 + (r2 / r1)) # Calculate the current flowing through R2B2, this is # the same as the current flowing through R2B. i = iparallel( # The voltage is that flowing across R2 vseries(vout, r1, r2), # The resistors in the network r2a, r2b ) # Calculate the voltage drop across R2B2 v = vseries( # The voltage is that flowing across R2 vseries(vout, r1, r2), # The resistors in the network r2b1, r2b2 ) # All done return vout, i, v def digipot_range(r1, r2a, r2b1, r2b2): """ Calculate the maximum voltage drop and current passing through the digipot (r2b2) and the output voltage range of the regulator. The 'r2b2' parameter is the maximum range of the digipot. """ vmin, i1, v1 = digipot(r1, r2a, r2b1, 0.0) vmax, i2, v2 = digipot(r1, r2a, r2b1, r2b2) return vmin, vmax, max(i1, i2), max(v1, v2)
The first function, digipot(), calculates the output voltage of the regulator as well as the current and voltage flowing through the digipot. The second function, digipot_range(), simply does this twice - once at each end of the digipot range and returns the voltage range from the regulator as well as the maximum current and voltage passing through the digipot.
Rather than simply plug in values for the resistors in the network and see what the results where I decided to use a brute force approach to find the best set of values I could. To do this I simply ran through all possible combinations of standard resistor values for each of the resistors in the network, calculated the results for each combination, assigned the result a score based on how well it met the requirements and picked the highest scoring combination.
def score(vmin, vmax, ir2b2, vr2b2): """ Assign a score to the results. The higher the score the better. """ result = 0.0 if (vmin <= 3.0) and (vmax <= 11.0): result = result + 1.0 + ((vmax - vmin) / 7.5) if ir2b2 <= 0.0025: result = result + 1.0 if vr2b2 <= 5.0: result = result + 1.0 return result
The score is calculated using the code above. I assign one point for each of the following criteria:
- The voltage range starts below 3.0V and does not exceed 11.0V (I'm using a 12V input voltage so there is an upper limit on the regulated output).
- The maximum current over the digipot does not exceed 2.5mA.
- The maximum voltage over the digipot does not exceed 5.0V.
On top of this I add bonus points for the voltage range - the wider the range the higher the score. After running the script (it only takes a few seconds) this is the end result:
New best score: 3.61 Inputs: r1 = 1000, r2a = 22000, r2b1 = 560, r2b2 = 5000 Results: Vmin = 1.93, Vmax = 6.80, I = 0.0012, V = 4.99
With these resistor values and a 5K digipot I can control the output voltage from 1.93V to 6.80V without exceeding the operating limits of the digipot. It's not as wide a voltage range as I had hoped for but it is enough to simulate a battery pack discharging (which was my original use case). At least now I can build up the circuit to experiment with and be relatively certain I'm not going to blow any components.
I had a few comments on the Google+ post about the design suggesting that I use the digipot to control a transistor acting as a variable resistor. The transistor would have much higher specs allowing for a greater output voltage range and it would be a lot easier to control the current and voltage through the digipot. I will be investigating this further and most likely use the same techniques described above to calculate the optimal component values for that solution as well.