lambert_w_diode_graph.cpp 9.5 KB

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