PageRenderTime 86ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/firmware/robot2011/signal_captures/plot-kicker/plot-kicker.py

https://gitlab.com/balajimude.it/robocup-firmware
Python | 224 lines | 112 code | 49 blank | 63 comment | 3 complexity | 7021edbdcb5f32d7eb253d696f28f24b MD5 | raw file
  1. #!/usr/bin/env python
  2. # The electrical side of the kicker is a series RLC circuit.
  3. # The current around the loop is measured directly.
  4. #
  5. # The measured voltage, however, does not map to the voltage across any ideal component.
  6. # The capacitor and inductor each have an internal resistance.
  7. # Voltage is measured between these two unknown resistances.
  8. import scipy.integrate
  9. import scipy.optimize
  10. from numpy import *
  11. from matplotlib.pyplot import *
  12. # Channel 1 (row 0): current, 100A/V
  13. # Channel 2 (row 1): voltage, 1V/V
  14. # 200ksamples/s
  15. assert len(sys.argv) == 2
  16. # Find the beginning of discharge
  17. data = loadtxt(sys.argv[1])
  18. for start in range(size(data, 1)):
  19. if data[1, start] > 10:
  20. break
  21. # Number of post-trigger (useful) samples
  22. num_samples = size(data, 1) - start
  23. # Sample rate, fixed by scope configuration
  24. sample_rate = 200e3
  25. # Current sense resistor
  26. rsense = 0.010
  27. # Time of each sample
  28. tt = arange(0, num_samples) / sample_rate
  29. # print arange(0, num_samples),num_samples
  30. # Time of last sample
  31. tmax = (num_samples - 1) / sample_rate
  32. # Current out of the capacitor
  33. i_rlc = array(data[0, start:] / rsense)
  34. # Voltage across the capacitor and its internal resistance
  35. v = array(data[1, start:])
  36. # Maximum current
  37. imax = max(i_rlc)
  38. # Index of maximum current
  39. imax_pos = list(i_rlc).index(imax)
  40. # Find the parameters of the damped oscillation with least squares estimation.
  41. # This depends only on the measured current, so it will not be affected by the unknown
  42. # resistances in the capacitor and inductor.
  43. def residual_i(X):
  44. (wd, a, s) = X
  45. fit_i = exp(-a * tt) * sin(wd * tt) * s
  46. return i_rlc - fit_i
  47. ((wd, a, iscale), success) = scipy.optimize.leastsq(residual_i, [100, 100,
  48. 1000])
  49. print wd, a, iscale, success
  50. print residual_i
  51. assert success
  52. # Best-fit current
  53. fit_i = exp(-a * tt) * sin(wd * tt) * iscale
  54. fit_imax = max(fit_i)
  55. # Index of largest best-fit current.
  56. # This is the position where the inductor starts discharging.
  57. fit_imax_pos = list(fit_i).index(fit_imax)
  58. fit_imax_time = fit_imax_pos / sample_rate
  59. # Best-fit current evaluated at an arbitrary time.
  60. # Use this for integration.
  61. def eval_fit_i(t):
  62. return exp(-a * t) * sin(wd * t) * iscale
  63. # Maximum voltage
  64. vmax = max(v)
  65. # Calculate capacitance by assuming the voltage decays to zero over a long time.
  66. # Integrate the current over time to find the capacitance required to change from vmax to 0 volts.
  67. # In theory, the total delivered energy would work, but there is significant integration error.
  68. #
  69. # vmax - scipy.integrate.quad(eval_fit_i, 0, 1) / c = 0
  70. # vmax * c = scipy.integrate.quad(eval_fit_i, 0, 1)
  71. c = scipy.integrate.quad(eval_fit_i, 0, 1)[0] / vmax
  72. # Find the total charge that has left the capacitor at each time.
  73. # Use the best-fit current for more accurate integration.
  74. charge = array([scipy.integrate.quad(eval_fit_i, 0, t)[0] for t in tt])
  75. #charge = array([scipy.integrate.trapz(fit_i[:n], dx=1/sample_rate) for n in range(num_samples)])
  76. def residual_c(X):
  77. #(vmax, c, rc) = X
  78. rc = X
  79. return v - ((vmax - charge / c) - i_rlc * rc)
  80. # It sounds like a good idea to fit Vmax, C, and Rc together, but this results in larger total energy errors
  81. # than if we use Vmax and C as found above and only use least squares to estimate Rc.
  82. #((vmax, c, rc), success) = scipy.optimize.leastsq(residual_c, [vmax, 2*2400e-6, 0.1])
  83. # Find the capacitor's ESR (Rc).
  84. ((rc), success) = scipy.optimize.leastsq(residual_c, [0.1])
  85. assert success
  86. # Best-fit voltage across ideal capacitance
  87. fit_vc = vmax - charge / c
  88. # Best-fit voltage across measurement point
  89. fit_v = fit_vc - fit_i * rc
  90. # Find dampled-sinusoid parameters w0 and z
  91. # a = w0 * z
  92. # w0 = a / z
  93. # wd = w0 * sqrt(1 - z**2)
  94. # wd = a / z * sqrt(1 - z**2)
  95. # wd / a = sqrt(1 - z**2) / z
  96. z = sqrt(1 / ((wd / a)**2 + 1))
  97. w0 = wd / sqrt(1 - z**2)
  98. # w0 = 1/sqrt(L*c)
  99. L = 1.0 / (c * w0**2)
  100. # Total resistance (Rc + Rl)
  101. # z = R / (2 * L * w0)
  102. r = z * (2 * L * w0)
  103. # Inductor resistance
  104. rl = r - rc - rsense
  105. # Power out of the capacitor+Rc according to curve fit
  106. fit_p = fit_v * fit_i
  107. # Power dissipated in the total resistance
  108. pr = i_rlc**2 * r
  109. fit_pr = fit_i**2 * r
  110. # Power leaving the capacitor (excluding loss in Rc)
  111. pc = i_rlc * (v + i_rlc * rc)
  112. fit_pc = fit_i * fit_vc
  113. # Power delivered to ideal inductance
  114. fit_pl = fit_pc - fit_pr
  115. fit_vl = fit_vc - fit_i * r
  116. ec_max = c * vmax**2 / 2
  117. el_max = scipy.integrate.trapz(fit_pl[:fit_imax_pos], dx=1 / sample_rate)
  118. # Energy left in the capacitor if we cut off current at Imax
  119. cutoff_final_energy = c * fit_vc[fit_imax_pos]**2 / 2
  120. print 'Pre-trigger samples %d' % start
  121. print 'Peak voltage: %5.1f V' % vmax
  122. print 'Peak current: %5.1f A' % imax
  123. print 'Peak power out of cap: %5.1f kW' % (max(fit_pc) / 1e3)
  124. print 'Peak power to inductor: %5.1f kW' % (max(fit_pl) / 1e3)
  125. print 'Mean pos inductor power:%5.1f kW' % (el_max / fit_imax_time / 1e3)
  126. print 'Peak ohmic loss: %5.1f kW' % (max(fit_pr) / 1e3)
  127. print 'Natural frequency: %5.1f Hz' % w0
  128. print 'Damped frequency: %5.1f Hz' % wd
  129. print 'Alpha (zeta*w0): %7.3f' % a
  130. print 'Damping ratio (zeta): %7.3f' % z
  131. print 'Current curve scale: %8.3f' % iscale
  132. print 'Capacitance (each): %6.1f uF' % (c * 1e6 / 2)
  133. print 'Inductance: %5.1f uH' % (L * 1e6)
  134. print 'Sense resistor: %2.0f milliohms' % (rsense * 1e3)
  135. print 'Capacitor resistance: %5.2f milliohms' % (rc * 1e3)
  136. print 'Inductor resistance: %6.2f milliohms' % (rl * 1e3)
  137. print 'Total Resistance: %6.2f milliohms' % (r * 1e3)
  138. print 'Initial energy: %6.2f J' % (0.5 * c * vmax**2)
  139. print 'Total energy delivered: %6.2f J' % scipy.integrate.trapz(
  140. pc,
  141. dx=1 / sample_rate)
  142. print 'Total loss: %6.2f J' % scipy.integrate.trapz(
  143. fit_pr,
  144. dx=1 / sample_rate)
  145. print 'Final inductor energy: %6.2f J' % scipy.integrate.trapz(
  146. fit_pl,
  147. dx=1 / sample_rate)
  148. print 'Max inductor energy: %6.2f J' % el_max
  149. # Efficiency if we use all energy in the inductor
  150. print 'Max efficiency: %4.1f%%' % (el_max / ec_max * 100)
  151. # Efficiency if we cut off current at Imax, so the capacitor does not fully discharge
  152. print 'Cutoff efficiency: %4.1f%%' % (el_max / (
  153. ec_max - cutoff_final_energy) * 100)
  154. fig = figure()
  155. fig.canvas.set_window_title(sys.argv[1])
  156. subplot(311)
  157. plot(tt, i_rlc, 'g', tt, fit_i, 'r')
  158. vlines(fit_imax_time, 0, fit_imax)
  159. grid(True)
  160. ylabel('Current (A)')
  161. legend(['Measured', 'Best fit'])
  162. subplot(312)
  163. plot(tt, v, 'g', tt, fit_v, 'r')
  164. vlines(fit_imax_time, 0, fit_v[fit_imax_pos])
  165. grid(True)
  166. ylabel('Voltage (V)')
  167. legend(['Measured', 'Best fit'])
  168. subplot(313)
  169. plot(tt, fit_pc / 1e3, 'g', tt, fit_pl / 1e3, 'r', tt, fit_pr / 1e3, 'b')
  170. vlines(fit_imax_time, 0, fit_pr[fit_imax_pos] / 1e3)
  171. grid(True)
  172. legend(['Out of capacitor', 'Into inductor', 'Lost in resistance'])
  173. ylabel('Power (kW)')
  174. xlabel('Time (s)')
  175. show()