Processing math: 100%

Tuesday, November 25, 2014

Experimenting with Diodes and Non-Linear Curve Fitting

How much does a diode's characteristics actually diviate from diode to diode? I measured a small number of diodes, and then recovered the SPICE DC parameters. Due to the limitations of my equipment, I only did this in the DC forward biased region. I'll also describe how to accurately recover the SPICE parameters using numerical methods.

Forward bias SPICE Diode Model at DC

SPICE models a diode using the Shockley diode equation with a series resistor. That is, I=Is(eVDIRsnkbT1)

Where:

  • I [A] is the current through the diode.
  • VD [V] is the voltage across the entire diode (i.e. voltage across diode junction and internal series resistance)
  • Is [A] is the reverse biase saturation current/scale current.
  • Rs [Ω] is the series resistance.
  • n is the emission coefficient.
  • kb [eVK] is Boltzmann's constant.
  • T [K] is the absolute temperature of the junction.
Unfortunately, many of these parameters are not given in diode datasheets, let alone any usable tolerances. However, they do usually have a V-I diagram (plot of VD vs. I). Now inverting this equation is a bit tricky... as far as I'm aware, there's not a closed form solution that can be used for curve fitting. However, we can use Newton's method with least squares to get an accurate curve fit.

Non-linear curve fitting (aside)

Re-arranging the model equation in the form of a root equation (note: it's not necessary to do this level of re-arrangement; simply moving I over to the right would have worked, too), 0=IRs+ln(IIs+1)nkbTV

The first step is to take multiple measurements of VD vs. I, either with a real diode by picking data from a datasheet. Then, write a set of m equations (where m is the number of datapoints): 0=I1Rs+ln(I1Is+1)nkbTV10=I2Rs+ln(I2Is+1)nkbTV20=ImRs+ln(ImIs+1)nkbTVm
Newton's method in 1D (1 independent variable) is pretty simple: xi+1=xi+yiyi
All we need to do is generalize this to multiple variables. This is done by writing a Jacobian matrix: J=[y1x1y1x2y1xjy2x1y2x2y2xjymx1ymx2ymxj]
For j is the number of independent variables. In our case, the independent variables we have are n, Rs, and Is, so i=3. Now we can't "divide" matrices, but we can multiple by the inverse (note, this derivation is a slight lie because there isn't an inverse for non-square matrices), so Newton's method becomes: xi+1=xi+J1iyi
Rather than compute the inverse of J1i, we can re-arrange and solve a system of equations. Jixi+1=Jixi+yi
Everything on the right is known, so simply compute this and solve the system. The careful reader might want to know what do if Ji is not square. We can use least squares. This turns the system into a system Ax=b, where now we do have a square matrix. We solve the normal equation: JTiJixi+1=JTiJixi+JTiyi
However, this is poorly conditioned and if you use a computer to try and solve this, it's likely your answer will be off due to rounding. We can improve our answer using QR factorization (note: here, I'm using the "square" version of QR factorization, where R gets zero-padded). QiRi=Ji
RTiQTiQiRixi+1=RTiQTiQiRixi+RTiQTiyi
Due to some great properties of Qi (it's orthonormal), RTiRixi+1=RTiRixi+RTiQTiyi
However, everything has the same factor of RTi at the front, so we can remove this: Rixi+1=Rixi+QTiyi
Which along with being numerically much better (it's condition number is O(κ) instead of O(κ2) for the naive implementation), is also easier to solve because Ri is upper triangular (granted, the QR factorization is not necessarily easy, so it's not actually much easier to solve if you have to write the QR factorization algorithm). Incidentally, the number of required data points is mj.

Implementation

The Jacobian matrix for our diode model is: Ji=[ln(I1Is,i+1)kbTI1I1nikbTI1Is,i+I2s,iln(I2Is,i+1)kbTI2I2nikbTI2Is,i+I2s,iln(ImIs,i+1)kbTImImnikbTImIs,i+I2s,i]
Even though I've explained the mathematical theory for how to perform a non-linear curve fit, the code I used actually uses the SciPy leastsq function. This does effectively what I described, though it may do it in a slightly different fashion. The only inputs are a function for evaluating yi and Ji (technically you don't need to provide a function for evaluating Ji, but it will converge quicker if you do).
# Yes, I know. I'm lazy.
from numpy import *
from scipy.optimize import leastsq

def diode_res(args, T, V, I):
    '''
    Shockley diode equation with series resistor residual

    @param n emission coefficient [unitless]
    @param Rs series resistance [ohms]
    @param Is saturation current [A]
    @param T reference temperature [K]
    @param V voltage across diode [V]
    @param I current through diode [A]
    
    @return I*Rs+log(I/Is+1)*n*k*T-V
    '''
    n, Rs, Is = args
    kb = 8.617332478e-5
    return I*Rs+log(I/Is+1)*n*kb*T-V

def jac_diode(args, T, V, I):
    '''
    Jacobian of the diode equation.
    Independent vars: n, Rs, Is
    
    @param n emission coefficient [unitless]
    @param Rs series resistance [ohms]
    @param Is saturation current [A]
    @param T reference temperature [K]
    @param V voltage across diode [V]
    @param I current through diode [A]

    @return Jacobian matrix transposed
    '''
    n,Rs,Is = args
    
    kb = 8.617332478e-5
    res = zeros([3,len(V)])
    res[0,:] = log(I/Is+1)*kb*T
    res[1,:] = I
    res[2,:] = -I/(I*Is+Is**2)*n*kb*T
    return res

def diode_coeffs(V, I, T):
    '''
    Find diode coefficients given V vs. I at a specified T.
    @param V
    @param I
    @param T in K
    @return n, Rs, Is
    '''
    return leastsq(diode_res, array([1,0,1e-14]),args=(T,V,I), Dfun=jac_diode, col_deriv=True,xtol=1e-15)[0]

Experimental Setup

The setup couldn't be any easier:

Figure 1. Test ciruit
My power supply (Korad KA3005D-3S) is capable of measuring Vs to ±0.01V, and my multimeter (Extech MN36) is capable of measuring current to ±0.01mA or ±0.1mA, depending on the range. You may immediately recognize that I am not using a second meter to simultaneously measure VD, instead relying on the power supply meter! This means I will have to take the burden voltage of the internal shunt resistor into account. The reason I don't use a second multimeter to measure VD is because I don't have one. It's not an ideal setup, but it does work. The power supply was set to the following voltages for every diode: 0.6V, 0.7V, 0.8V, 1V, 1.5V, 2V, 2.5V, and 3V.

The diodes tested were the 1N4148 made by Fairchild Semiconductor.

Data

The data was measured at T=19C±1C. Table 1 shows the measured currents at the various voltages for each diode tested. The measured lead resistance (using a 4-wire measurement with the power supply panel meter and multimeter) was 17.319Ω.

Table 1. Measured currents [mA]
0.6V 0.7V 0.8V 0.9V 1V 1.5V 2V 2.5V 3V
Diode 1 0.52 2.31 5.58 9.82 14.46 39.8 66.5 93.8 121
Diode 2 0.56 2.43 5.75 9.98 14.58 39.7 66 93 119.8
Diode 3 0.54 2.4 5.7 9.91 14.54 39.8 66.1 93.3 120.3
Diode 4 0.49 2.27 5.54 9.75 14.38 39.98 66.1 93.3 120.3
Diode 5 0.54 2.38 5.69 9.95 14.6 40 66.7 94 121.1
Diode 6 0.57 2.46 5.8 10.06 14.7 40 66.5 39.7 120.8
Diode 7 0.52 2.32 5.61 9.85 14.5 39.9 66.4 93.6 120.9

Analysis

Figure 2 shows the reconstructed I-V curves, demonstrating that the technique appears to work.

Figure 2. Reconstructed I-V curves at 25C.
Figure 3 shows the relative deviation of the physically tested diodes vs. the manufacturer's SPICE model. This indicates there is likely some sort of systematic error in the test setup. Possible reasons for this error include assuming the given constants are constant vs. temperature, the accuracy issues associated with using a low-resolution panel meter for measuring voltage, and doing a round-about reconstruction for VD using a constant measured shunt resistance. Also, because these are relative differences small errors become amplified for small input currents. Ignoring the relative issues at really low currents, the deviations really are quite small, being withing 10% down to 0.1 mA.
Figure 3. Relative deviation from manufacturer's SPICE model.
Figure 4 comparing just the physical diodes vs. the mean measured parameters. This removes most of the systematic contributions to deviation, and the batch I tested was within 10% deviation all the way down to 0.6 μA, due to one outlier. Without this outlier the error region is much lower at 2 nA.
Figure 4. Relative deviation from mean measured parameters.
Table 2 shows statistics on the measured model parameters. This shows there are significant relative deviations in Rs and Is. Deviations are due to physical placement of the leads (may not have been the same everytime, leading to different lead resistances), as well as the issues due to the current shunt resistance. Self heating could also play a factor as the measured currents went up to 120 mA, leading to ~111 mW dissipated in the diode.
Table 2. Statistics on measured diode parameters.
n Rs Is
mean 1.7875 0.636 Ω 1.081 nA
std. deviation 0.022 0.0913 Ω 0.1924 nA
relative error (std.dev.mean) 1.23% 14.4% 17.8%

Conclusion

This test does provide a convenient rough bound on what should be relatively easy to achieve in practice. The parameters are certainly within an order of magnitude of each other. Unfortunately, it's hard to determine if most of the deviation errors in the diode model parameters are due to experimental setup or intrinsic to the diodes. Additionally, 7 test samples is hardly what could be called a statistically significant quantity. The test setup was quite limited because I lacked a second DMM to directly measure the diode voltage, instead relying on a fixed shunt resistance that was measured before-hand. Comparisons to the datasheet and manufacturer SPICE model had some sort of systematic error. This could have been associated with assuming the model parameters are constant vs. temperature, where in reality they could drift. There was significant self heating and getting an accurate junction temperature was not possible.

2 comments :

  1. Which version of Python, numpy, and scipy are you using? The code you've posted errors with TypeError: 'numpy.float32' object cannot be interpreted as an integer (traceback is pointing to the line "return I*Rs+log(I/Is+1)*n*kb*T-V" in diode_res(), but seems to actually be something wrong with leastsq(), since I can call diode_res() just fine.

    ReplyDelete
    Replies
    1. I tried with Python 3 and Python 2.7 and both of them work. I have SciPy 0.18.1, but I think at the time I wrote this it was SciPy 0.15.0?

      Delete