1 // Copyright Paul A. Bristow 2016
2 // Copyright John Z. Maddock 2016
3
4 // Distributed under the Boost Software License, Version 1.0.
5 // (See accompanying file LICENSE_1_0.txt or
6 // copy at http ://www.boost.org/LICENSE_1_0.txt).
7
8 /*! \brief Graph showing use of Lambert W function to compute current
9 through a diode-connected transistor with preset series resistance.
10
11 \details T. C. Banwell and A. Jayakumar,
12 Exact analytical solution of current flow through diode with series resistance,
13 Electron Letters, 36(4):291-2 (2000).
14 DOI: doi.org/10.1049/el:20000301
15
16 The current through a diode connected NPN bipolar junction transistor (BJT)
17 type 2N2222 (See https://en.wikipedia.org/wiki/2N2222 and
18 https://www.fairchildsemi.com/datasheets/PN/PN2222.pdf Datasheet)
19 was measured, for a voltage between 0.3 to 1 volt, see Fig 2 for a log plot, showing a knee visible at about 0.6 V.
20
21 The transistor parameter I sat was estimated to be 25 fA and the ideality factor = 1.0.
22 The intrinsic emitter resistance re was estimated from the rsat = 0 data to be 0.3 ohm.
23
24 The solid curves in Figure 2 are calculated using equation 5 with rsat included with re.
25
26 http://www3.imperial.ac.uk/pls/portallive/docs/1/7292572.PDF
27
28 */
29
30 #include <boost/math/special_functions/lambert_w.hpp>
31 using boost::math::lambert_w0;
32 #include <boost/math/special_functions.hpp>
33 using boost::math::isfinite;
34 #include <boost/svg_plot/svg_2d_plot.hpp>
35 using namespace boost::svg;
36
37 #include <iostream>
38 // using std::cout;
39 // using std::endl;
40 #include <exception>
41 #include <stdexcept>
42 #include <string>
43 #include <array>
44 #include <vector>
45 #include <utility>
46 using std::pair;
47 #include <map>
48 using std::map;
49 #include <set>
50 using std::multiset;
51 #include <limits>
52 using std::numeric_limits;
53 #include <cmath> //
54
55 /*!
56 Compute thermal voltage as a function of temperature,
57 about 25 mV at room temperature.
58 https://en.wikipedia.org/wiki/Boltzmann_constant#Role_in_semiconductor_physics:_the_thermal_voltage
59
60 \param temperature Temperature (degrees Celsius).
61 */
v_thermal(double temperature)62 const double v_thermal(double temperature)
63 {
64 BOOST_CONSTEXPR const double boltzmann_k = 1.38e-23; // joules/kelvin.
65 BOOST_CONSTEXPR double charge_q = 1.6021766208e-19; // Charge of an electron (columb).
66 double temp = +273; // Degrees C to K.
67 return boltzmann_k * temp / charge_q;
68 } // v_thermal
69
70 /*!
71 Banwell & Jayakumar, equation 2, page 291.
72 */
i(double isat,double vd,double vt,double nu)73 double i(double isat, double vd, double vt, double nu)
74 {
75 double i = isat * (exp(vd / (nu * vt)) - 1);
76 return i;
77 } //
78
79 /*!
80 Banwell & Jayakumar, Equation 4, page 291.
81 i current flow = isat
82 v voltage source.
83 isat reverse saturation current in equation 4.
84 (might implement equation 4 instead of simpler equation 5?).
85 vd voltage drop = v - i* rs (equation 1).
86 vt thermal voltage, 0.0257025 = 25 mV.
87 nu junction ideality factor (default = unity), also known as the emission coefficient.
88 re intrinsic emitter resistance, estimated to be 0.3 ohm from low current.
89 rsat reverse saturation current
90
91 \param v Voltage V to compute current I(V).
92 \param vt Thermal voltage, for example 0.0257025 = 25 mV, computed from boltzmann_k * temp / charge_q;
93 \param rsat Resistance in series with the diode.
94 \param re Intrinsic emitter resistance (estimated to be 0.3 ohm from the Rs = 0 data)
95 \param isat Reverse saturation current (See equation 2).
96 \param nu Ideality factor (default = unity).
97
98 \returns I amp as function of V volt.
99 */
100
101 //[lambert_w_diode_graph_2
iv(double v,double vt,double rsat,double re,double isat,double nu=1.)102 double iv(double v, double vt, double rsat, double re, double isat, double nu = 1.)
103 {
104 // V thermal 0.0257025 = 25 mV
105 // was double i = (nu * vt/r) * lambert_w((i0 * r) / (nu * vt)); equ 5.
106
107 rsat = rsat + re;
108 double i = nu * vt / rsat;
109 // std::cout << "nu * vt / rsat = " << i << std::endl; // 0.000103223
110
111 double x = isat * rsat / (nu * vt);
112 // std::cout << "isat * rsat / (nu * vt) = " << x << std::endl;
113
114 double eterm = (v + isat * rsat) / (nu * vt);
115 // std::cout << "(v + isat * rsat) / (nu * vt) = " << eterm << std::endl;
116
117 double e = exp(eterm);
118 // std::cout << "exp(eterm) = " << e << std::endl;
119
120 double w0 = lambert_w0(x * e);
121 // std::cout << "w0 = " << w0 << std::endl;
122 return i * w0 - isat;
123 } // double iv
124
125 //] [\lambert_w_diode_graph_2]
126
127
128 std::array<double, 5> rss = { 0., 2.18, 10., 51., 249 }; // series resistance (ohm).
129 std::array<double, 7> vds = { 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 }; // Diode voltage.
130 std::array<double, 7> lni = { -19.65, -15.75, -11.86, -7.97, -4.08, -0.0195, 3.6 }; // ln(current).
131
main()132 int main()
133 {
134 try
135 {
136 std::cout << "Lambert W diode current example." << std::endl;
137
138 //[lambert_w_diode_graph_1
139 double nu = 1.0; // Assumed ideal.
140 double vt = v_thermal(25); // v thermal, Shockley equation, expect about 25 mV at room temperature.
141 double boltzmann_k = 1.38e-23; // joules/kelvin
142 double temp = 273 + 25;
143 double charge_q = 1.6e-19; // column
144 vt = boltzmann_k * temp / charge_q;
145 std::cout << "V thermal " << vt << std::endl; // V thermal 0.0257025 = 25 mV
146 double rsat = 0.;
147 double isat = 25.e-15; // 25 fA;
148 std::cout << "Isat = " << isat << std::endl;
149 double re = 0.3; // Estimated from slope of straight section of graph (equation 6).
150 double v = 0.9;
151 double icalc = iv(v, vt, 249., re, isat);
152 std::cout << "voltage = " << v << ", current = " << icalc << ", " << log(icalc) << std::endl; // voltage = 0.9, current = 0.00108485, -6.82631
153 //] [/lambert_w_diode_graph_1]
154
155 // Plot a few measured data points.
156 std::map<const double, double> zero_data; // Extrapolated from slope of measurements with no external resistor.
157 zero_data[0.3] = -19.65;
158 zero_data[0.4] = -15.75;
159 zero_data[0.5] = -11.86;
160 zero_data[0.6] = -7.97;
161 zero_data[0.7] = -4.08;
162 zero_data[0.8] = -0.0195;
163 zero_data[0.9] = 3.9;
164
165 std::map<const double, double> measured_zero_data; // No external series resistor.
166 measured_zero_data[0.3] = -19.65;
167 measured_zero_data[0.4] = -15.75;
168 measured_zero_data[0.5] = -11.86;
169 measured_zero_data[0.6] = -7.97;
170 measured_zero_data[0.7] = -4.2;
171 measured_zero_data[0.72] = -3.5;
172 measured_zero_data[0.74] = -2.8;
173 measured_zero_data[0.76] = -2.3;
174 measured_zero_data[0.78] = -2.0;
175 // Measured from Fig 2 as raw data not available.
176
177 double step = 0.1;
178 for (int i = 0; i < vds.size(); i++)
179 {
180 zero_data[vds[i]] = lni[i];
181 std::cout << lni[i] << " " << vds[i] << std::endl;
182 }
183 step = 0.01;
184
185 std::map<const double, double> data_2;
186 for (double v = 0.3; v < 1.; v += step)
187 {
188 double current = iv(v, vt, 2., re, isat);
189 data_2[v] = log(current);
190 // std::cout << "v " << v << ", current = " << current << " log current = " << log(current) << std::endl;
191 }
192 std::map<const double, double> data_10;
193 for (double v = 0.3; v < 1.; v += step)
194 {
195 double current = iv(v, vt, 10., re, isat);
196 data_10[v] = log(current);
197 // std::cout << "v " << v << ", current = " << current << " log current = " << log(current) << std::endl;
198 }
199 std::map<const double, double> data_51;
200 for (double v = 0.3; v < 1.; v += step)
201 {
202 double current = iv(v, vt, 51., re, isat);
203 data_51[v] = log(current);
204 // std::cout << "v " << v << ", current = " << current << " log current = " << log(current) << std::endl;
205 }
206 std::map<const double, double> data_249;
207 for (double v = 0.3; v < 1.; v += step)
208 {
209 double current = iv(v, vt, 249., re, isat);
210 data_249[v] = log(current);
211 // std::cout << "v " << v << ", current = " << current << " log current = " << log(current) << std::endl;
212 }
213
214 svg_2d_plot data_plot;
215
216 data_plot.title("Diode current versus voltage")
217 .x_size(400)
218 .y_size(300)
219 .legend_on(true)
220 .legend_lines(true)
221 .x_label("voltage (V)")
222 .y_label("log(current) (A)")
223 //.x_label_on(true)
224 //.y_label_on(true)
225 //.xy_values_on(false)
226 .x_range(0.25, 1.)
227 .y_range(-20., +4.)
228 .x_major_interval(0.1)
229 .y_major_interval(4)
230 .x_major_grid_on(true)
231 .y_major_grid_on(true)
232 //.x_values_on(true)
233 //.y_values_on(true)
234 .y_values_rotation(horizontal)
235 //.plot_window_on(true)
236 .x_values_precision(3)
237 .y_values_precision(3)
238 .coord_precision(4) // Needed to avoid stepping on curves.
239 .copyright_holder("Paul A. Bristow")
240 .copyright_date("2016")
241 //.background_border_color(black);
242 ;
243
244 // ₀ = subscript zero.
245 data_plot.plot(zero_data, "I₀(V)").fill_color(lightgray).shape(none).size(3).line_on(true).line_width(0.5);
246 data_plot.plot(measured_zero_data, "Rs=0 Ω").fill_color(lightgray).shape(square).size(3).line_on(true).line_width(0.5);
247 data_plot.plot(data_2, "Rs=2 Ω").line_color(blue).shape(none).line_on(true).bezier_on(false).line_width(1);
248 data_plot.plot(data_10, "Rs=10 Ω").line_color(purple).shape(none).line_on(true).bezier_on(false).line_width(1);
249 data_plot.plot(data_51, "Rs=51 Ω").line_color(green).shape(none).line_on(true).line_width(1);
250 data_plot.plot(data_249, "Rs=249 Ω").line_color(red).shape(none).line_on(true).line_width(1);
251 data_plot.write("./diode_iv_plot");
252
253 // bezier_on(true);
254 }
255 catch (std::exception& ex)
256 {
257 std::cout << ex.what() << std::endl;
258 }
259
260
261 } // int main()
262
263 /*
264
265 //[lambert_w_output_1
266 Output:
267 Lambert W diode current example.
268 V thermal 0.0257025
269 Isat = 2.5e-14
270 voltage = 0.9, current = 0.00108485, -6.82631
271 -19.65 0.3
272 -15.75 0.4
273 -11.86 0.5
274 -7.97 0.6
275 -4.08 0.7
276 -0.0195 0.8
277 3.6 0.9
278
279 //] [/lambert_w_output_1]
280 */
281