You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

reader_interface.py 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. """!
  2. @brief
  3. @data 14. Nov. 2016
  4. @author amueller
  5. """
  6. from threading import Thread, RLock
  7. from struct import pack, unpack
  8. from copy import deepcopy
  9. import socket
  10. import time
  11. import numpy as np
  12. from goalref_sample import GoalrefSample
  13. import cmath
  14. # Constants
  15. # Message length and format
  16. GOALREF_PACKET_LENGTH = 680
  17. GOALREF_PACKET_FORMAT = "!170i"
  18. # Factor between raw reader output values and voltages
  19. GOALREF_VOLTAGE_FACTOR = 4.47 / 200300000
  20. # Guard time to wait between sending commands to the reader
  21. GOALREF_COMMAND_GUARD_DELAY = .3
  22. # Number of bits in the sine table index of the DCO
  23. GOALREF_TABLE_IDX_LEN = 16
  24. # Clock frequency of the DCO
  25. GOALREF_MAIN_CLOCK = 1e6
  26. # Addresses in FPGA configuration memory
  27. GOALREF_MEMORY = {
  28. 'leds': {
  29. 'enable': 0x00000000
  30. },
  31. 'exciter': {
  32. 'phaseInc': 0x00000100 + (0 << 5),
  33. 'phaseOff': 0x00000100 + (1 << 5),
  34. 'gain': 0x00000100 + (2 << 5),
  35. 'enable': 0x00000100 + (3 << 5)
  36. },
  37. 'mixer': {
  38. 'phaseInc': 0x00000200 + (0 << 5)
  39. },
  40. 'lc': {
  41. 'phaseInc': 0x00000300 + (0 << 5),
  42. 'phaseOff': 0x00000300 + (1 << 5),
  43. 'gain': 0x00000300 + (2 << 5),
  44. #'enable': lcBase + (3 << 5), #unused
  45. 'auto': 0x00000300 + (4 << 5)
  46. },
  47. 'testSig': {
  48. 'phaseInc': 0x00000400 + (0 << 5),
  49. 'enable': 0x00000400 + (1 << 5)
  50. },
  51. 'switchCtrlBase': 0x00000500,
  52. 'switchCtrl': {
  53. 'extRelay0': 0x00000500 + 0,
  54. 'extRelay1': 0x00000500 + 1,
  55. 'extRelay2': 0x00000500 + 2,
  56. 'extRelay3': 0x00000500 + 3,
  57. 'extRelay4': 0x00000500 + 4,
  58. 'mainRelay': 0x00000500 + 5,
  59. 'frameRelay': 0x00000500 + 6,
  60. 'swA0': 0x00000500 + 7,
  61. 'swA1': 0x00000500 + 8,
  62. 'swA2': 0x00000500 + 9,
  63. 'gateExcDisable': 0x00000500 + 10,
  64. 'gateCalEnable': 0x00000500 + 11
  65. },
  66. 'microblaze': {
  67. 'lcAuto': 0xffffff00 + (0 << 5)
  68. }
  69. }
  70. class ReaderInterface(Thread):
  71. '''!
  72. @brief Interfaces with a GoalRef reader unit via UDP for data collection and
  73. configuration of the reader.
  74. '''
  75. def __init__(self, reader_ip="192.168.50.10", reader_port=4321, bind_address="0.0.0.0", bind_port=1234,
  76. num_antennas=10, resistance=0, inductance=0, load_factor=1, channel_permutation=None):
  77. '''!
  78. @brief Initializes a ReaderInterface to communicate with a GoalRef reader at the
  79. given reader_ip and reader_port, listening for reader data at the given bind_address, bind_port.
  80. A thread is spawned to listen for incoming data packets from the reader.
  81. '''
  82. Thread.__init__(self)
  83. self._reader_ip = reader_ip
  84. self._reader_port = reader_port
  85. self._num_antennas = num_antennas
  86. self._channel_permutation = channel_permutation
  87. # Open UDP socket
  88. self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  89. self._socket.bind((bind_address, bind_port))
  90. self._socket.settimeout(3.0)
  91. # Initialize status
  92. self._readerAvailable = False
  93. self._dataRequests = []
  94. self._listLock = RLock()
  95. self._lastCommand = time.time()-GOALREF_COMMAND_GUARD_DELAY
  96. self._initialConfig = False
  97. self._commandCnt = 0
  98. # Status data from hardware
  99. self._lastPacketId = 0
  100. self._lastLanIdentifier = 0
  101. self._expectLanIdentifier = 0
  102. # Initialize configuration
  103. self._channels = [
  104. ChannelConfig(0, 70000, resistance, inductance,load_factor
  105. ),
  106. ChannelConfig(1, 119000, resistance, inductance, load_factor),
  107. ChannelConfig(2, 128000, resistance, inductance, load_factor),
  108. ChannelConfig(3, 134000, resistance, inductance, load_factor)
  109. ]
  110. self._switchCtrlMask = [0, 0, 0, 0, 0, -1, -1, 0, 0, 0, -1, -1]
  111. self._oldSwitchCtrlMask = deepcopy(self._switchCtrlMask)
  112. self._testSigEnable = np.zeros(21, dtype=np.uint32)
  113. self._oldTestSigEnable = deepcopy(self._testSigEnable)
  114. self._lcGain = -np.ones(16, dtype=np.uint32)
  115. self._oldLcGain = deepcopy(self._lcGain)
  116. # Start thread
  117. self._stopped = False
  118. #self.setDaemon(True)
  119. self.start()
  120. def run(self):
  121. # Loop while not stopped
  122. while not self._stopped:
  123. try:
  124. # Receive a packet
  125. data, addr = self._socket.recvfrom(4096)
  126. # Check if packet is from reader
  127. if addr[0] != self._reader_ip:
  128. print("Packet from unknown source: %s" % addr[0])
  129. continue
  130. if not self._readerAvailable:
  131. print("Reader available.")
  132. self._readerAvailable = True
  133. # Parse the packet and forward it
  134. self._processPacket(data)
  135. except socket.timeout:
  136. self._readerAvailable = False
  137. # If the reader is available and we may send a packet (guard delay expired)...
  138. if self._readerAvailable and self._lastCommand+GOALREF_COMMAND_GUARD_DELAY < time.time() and self._initialConfig:
  139. # ... check for changes in reader config
  140. for cc in self._channels:
  141. cmd = cc.getNextUpdatePacket()
  142. if cmd is not None:
  143. # Add command counter to packet
  144. self._commandCnt = (self._commandCnt + 1) % 0x7FFF
  145. addr = cmd[0] | (self._commandCnt << 16)
  146. self._expectLanIdentifier = (self._commandCnt << 16) | (cmd[1] & 0xFFFF)
  147. # Build payload for datagram
  148. payload = pack('!I', addr) + pack('!I', cmd[1])
  149. # Send payload to output
  150. self._socket.sendto(payload, (self._reader_ip, self._reader_port))
  151. self._lastCommand = time.time()
  152. break
  153. # If the reader is available and we may send a packet (guard delay expired)...
  154. # Caution: Separate from above condition because values may have changed.
  155. if self._readerAvailable and self._lastCommand+GOALREF_COMMAND_GUARD_DELAY < time.time() and self._initialConfig:
  156. cmd = self._getNextGlobalPacket()
  157. if cmd is not None:
  158. # Add command counter to packet
  159. self._commandCnt = (self._commandCnt + 1) % 0x7FFF
  160. addr = cmd[0] | (self._commandCnt << 16)
  161. self._expectLanIdentifier = (self._commandCnt << 16) | (cmd[1] & 0xFFFF)
  162. # Build payload for datagram
  163. payload = pack('!I', addr) + pack('!I', cmd[1])
  164. # Send payload to output
  165. self._socket.sendto(payload, (self._reader_ip, self._reader_port))
  166. self._lastCommand = time.time()
  167. def _processPacket(self, packet):
  168. # Check packet length
  169. if (len(packet) != GOALREF_PACKET_LENGTH):
  170. print('Ignoring wrong length GoalRef message: %d - expected %d' % (len(packet), GOALREF_PACKET_LENGTH))
  171. # Convert data packet to array of integers
  172. data = np.array(unpack(GOALREF_PACKET_FORMAT, packet))
  173. # First two bytes are status/header infos
  174. self._lastPacketId = data[0]
  175. if data[1] != self._lastLanIdentifier:
  176. if data[1] != self._expectLanIdentifier:
  177. print('INVALID IDENTIFIER RECEIVED: %08x (expected %08x).' % (data[1], self._expectLanIdentifier))
  178. else:
  179. print('Command accepted: %08x' % data[1])
  180. self._lastCommand = time.time()-GOALREF_COMMAND_GUARD_DELAY
  181. self._lastLanIdentifier = data[1]
  182. sampleLen = int((len(data)-2)/2)
  183. # Combine real and imaginary parts into complex numbers (ignore first two header bytes)
  184. sample = np.array(data[2:sampleLen+2])+1j*np.array(data[sampleLen+2:])
  185. # Scale input value to volts
  186. sample *= GOALREF_VOLTAGE_FACTOR
  187. # Wrap sample in GoalrefSample object
  188. sample = GoalrefSample(sample, self._num_antennas)
  189. # Rearrange channels as configured (if configured)
  190. if self._channel_permutation is not None:
  191. sample.applyPermutation(self._channel_permutation)
  192. # Forward data to requesting entities
  193. # At the same time remove requests which should not repeat
  194. with self._listLock:
  195. self._dataRequests = [x for x in self._dataRequests if x.addSample(sample)]
  196. def _getNextGlobalPacket(self):
  197. for i in range(len(self._switchCtrlMask)):
  198. if self._switchCtrlMask[i] != self._oldSwitchCtrlMask[i]:
  199. self._oldSwitchCtrlMask[i] = self._switchCtrlMask[i]
  200. print('Setting switch control for switch %d to %d' % (i, self._switchCtrlMask[i]))
  201. return (GOALREF_MEMORY['switchCtrlBase'] + i, self._switchCtrlMask[i])
  202. for i in range(len(self._lcGain)):
  203. if self._lcGain[i] != self._oldLcGain[i]:
  204. self._oldLcGain[i] = self._lcGain[i]
  205. print('Setting leakage cancellation gain for channel %d to %d' % (i, self._lcGain[i]))
  206. return (GOALREF_MEMORY['lc']['gain'] + i, self._lcGain[i])
  207. for i in range(len(self._testSigEnable)):
  208. if self._testSigEnable[i] != self._oldTestSigEnable[i]:
  209. self._oldTestSigEnable[i] = self._testSigEnable[i]
  210. print('Setting test signal enable for channel %d to %d' % (i, self._testSigEnable[i]))
  211. return (GOALREF_MEMORY['testSig']['enable'] + i, self._testSigEnable[i])
  212. return None
  213. def requestData(self, callback, blockSize=1, blocks=1):
  214. """!
  215. @brief To get datas from the reader.
  216. @param callback Function to be called when a block of data is available
  217. @param blockSize Number of samples to collect per block
  218. @param blocks Number of blocks to return. When -1 for unlimited
  219. @return request Object identifier for this request. Can be used for cancelRequest()
  220. """
  221. # Limit parameters to integers, negative blocks indicate infinite
  222. blockSize, blocks = int(blockSize), int(blocks)
  223. if blockSize < 1: blockSize = 1
  224. # Store data request as specified
  225. request = ReaderDataRequest(callback, blockSize, blocks)
  226. with self._listLock:
  227. self._dataRequests.append(request)
  228. return request
  229. def cancelRequest(self, request):
  230. '''!
  231. @brief To terminate the callback
  232. @param request Object identifier from requestData().
  233. '''
  234. if request != None:
  235. with self._listLock:
  236. self._dataRequests.remove(request)
  237. def setFrequency(self, channel, frequency):
  238. '''!
  239. @brief Set the frequency of a channel of the exciter
  240. @param channel Exciter channel. Choose between 0-3
  241. @param frequency Set the frequency
  242. '''
  243. self._channels[channel].setFrequency(frequency)
  244. def getFrequency(self, channel):
  245. '''!
  246. @brief Get the last software frequency value of a channel
  247. @param channel 0-3
  248. @param frequency Set the frequency 0-3
  249. '''
  250. return self._channels[channel].getFrequency()
  251. def setMixerFrequency(self, channel, frequency):
  252. '''!
  253. @brief
  254. @param channel 0-3
  255. @param frequency Set the frequency 0-3
  256. '''
  257. self._channels[channel].setMixerFrequency(frequency)
  258. def getMixerFrequency(self, channel):
  259. '''!
  260. @brief
  261. @param frequency Set the frequency
  262. '''
  263. return self._channels[channel].getMixerFrequency()
  264. def setExciterCurrent(self, channel, current):
  265. '''!
  266. @brief Set the current of the exciter
  267. @param channel
  268. @param current Exciter current which is set through the user.
  269. '''
  270. self._channels[channel].setExciterCurrent(current)
  271. def getExciterCurrent(self, channel):
  272. '''!
  273. @brief Get the last software current value of the exciter
  274. @param channel
  275. '''
  276. return self._channels[channel].getExciterCurrent()
  277. def setExciterGain(self, channel, gain):
  278. '''!
  279. @brief Set the amplitude of the exciter
  280. @param channel
  281. @param gain 0-65535
  282. '''
  283. self._channels[channel].setExciterGain(gain)
  284. def getExciterGain(self, channel):
  285. '''!
  286. @brief Get the last software amplitude value of the exciter
  287. @param channel
  288. '''
  289. return self._channels[channel].getExciterGain()
  290. def setExciterEnabled(self, channel, enable):
  291. '''!
  292. @brief
  293. @param channel Which
  294. @param enable True or False
  295. '''
  296. self._channels[channel].setExciterEnabled(enable)
  297. def isExciterEnabled(self, channel):
  298. '''!
  299. @brief
  300. @param channel Which
  301. '''
  302. return self._channels[channel].isExciterEnabled()
  303. def setTestSignalFrequency(self, channel, frequency):
  304. self._channels[channel].setTestSignalFrequency(frequency)
  305. def getTestSignalFrequency(self, channel):
  306. return self._channels[channel].getTestSignalFrequency()
  307. def setTestSignalEnabled(self, path, enable):
  308. self._testSigEnable[path] = 1 if enable else 0
  309. def isTestSignalEnabled(self, path):
  310. return (self._testSigEnable[path] == 1)
  311. def setMainRelay(self, value):
  312. """!
  313. @brief Set the main reader relay at the input of the reader to the calibration signal or antenna signal
  314. @param value Calibration signal = False, Antenna signal = True
  315. """
  316. self._switchCtrlMask[5] = 1 if value else 0
  317. def setFrameRelay(self, value):
  318. """!
  319. @brief Set the frame reader relay at the input of the reader to the calibration signal or antenna signal
  320. @param value Calibration signal = False, Antenna signal = True
  321. """
  322. self._switchCtrlMask[6] = 1 if value else 0
  323. def setExciterGating(self, excEnable, calEnable):
  324. """!
  325. @brief Turn the exciter and calibration signal on or off
  326. @param excEnable True - Enable exciter signal, False - Disable exciter signal
  327. @param calEnable True - Enable calibration signal, False - Disable calibration signal
  328. """
  329. self._switchCtrlMask[10] = 0 if excEnable else 1
  330. self._switchCtrlMask[11] = 1 if calEnable else 0
  331. def setChannel20Switch(self, value):
  332. """!
  333. @brief To switch the muliplexer of channal 20
  334. @return
  335. """
  336. self._switchCtrlMask[7] = 1 if value & 0x1 > 0 else 0
  337. self._switchCtrlMask[8] = 1 if value & 0x2 > 0 else 0
  338. self._switchCtrlMask[9] = 1 if value & 0x4 > 0 else 0
  339. def setLCGain(self, path, gain):
  340. if path > 15:
  341. raise Exception('Not implemented!')
  342. self._lcGain[path] = gain
  343. def getLCGain(self, path, gain):
  344. if path > 15:
  345. return 0
  346. return self._lcGain[path]
  347. def getMainRelay(self):
  348. """!
  349. @brief
  350. @return
  351. """
  352. return (self._switchCtrlMask[5] == 1)
  353. def getFrameRelay(self):
  354. """!
  355. @brief
  356. @return
  357. """
  358. return (self._switchCtrlMask[6] == 1)
  359. def getExciterGating(self):
  360. return (self._switchCtrlMask[10] == 0, self._switchCtrlMask[11] == 1)
  361. def getChannel20Switch(self):
  362. """!
  363. @brief
  364. @return
  365. """
  366. value = self._switchCtrlMask[7]
  367. value |= self._switchCtrlMask[8]<<1
  368. value |= self._switchCtrlMask[9]<<2
  369. return value
  370. def enableConfiguration(self):
  371. """!
  372. @brief
  373. """
  374. self._initialConfig = True
  375. def getNumAntennas(self):
  376. """!
  377. @brief
  378. @return
  379. """
  380. return self._num_antennas
  381. class ReaderDataRequest(object):
  382. def __init__(self, callback, blockSize, blocks):
  383. # Store parameters and initialize buffer
  384. self._callback = callback
  385. self._blockSize = blockSize
  386. self._blocks = blocks
  387. self._collectedSamples = []
  388. def addSample(self, sample):
  389. # Store sample in buffer
  390. self._collectedSamples.append(deepcopy(sample))
  391. # If buffer contains the requested number of samples, forward it
  392. if len(self._collectedSamples) == self._blockSize:
  393. # Trigger callback
  394. self._callback(self._collectedSamples)
  395. # Check if more blocks have been requested
  396. self._blocks -= 1
  397. if self._blocks == 0:
  398. return False
  399. self._collectedSamples = []
  400. return True
  401. class ChannelConfig(object):
  402. """!
  403. @brief
  404. @param object
  405. """
  406. def __init__(self, index, defaultFreq, resistance, inductance, load_factor):
  407. """!
  408. @param index
  409. @param defaultFreq
  410. @param resistance Resistance of the exciter.
  411. @param inductance Inductance of the exciter.
  412. @param load_factor Value of the load seen by the amplifier.
  413. """
  414. self._index = index
  415. self._resistance = resistance
  416. self._inductance = inductance
  417. self._load_factor = load_factor
  418. self._exciterFreq = defaultFreq
  419. self._exciterGain = 0
  420. self._exciterCurrent = None
  421. self._exciterEnable = False
  422. self._mixerFreq = defaultFreq
  423. self._testSigFreq = 0
  424. self._oldExciterFreq = None
  425. self._oldExciterGain = None
  426. self._oldExciterEnable = None
  427. self._oldMixerFreq = None
  428. self._oldTestSigFreq = None
  429. def setFrequency(self, frequency):
  430. self._exciterFreq = frequency
  431. self._mixerFreq = frequency
  432. if self._exciterCurrent is not None:
  433. self._exciterGain = self._calculateGain(self._exciterCurrent, frequency)
  434. def getFrequency(self):
  435. return self._exciterFreq
  436. def setMixerFrequency(self, frequency):
  437. self._mixerFreq = frequency
  438. def getMixerFrequency(self):
  439. return self._mixerFreq
  440. def setExciterCurrent(self, current):
  441. self._exciterCurrent = current
  442. self._exciterGain = self._calculateGain(current, self._exciterFreq)
  443. def getExciterCurrent(self):
  444. return self._exciterCurrent
  445. def setExciterGain(self, gain):
  446. self._exciterCurrent = None
  447. self._exciterGain = gain
  448. def getExciterGain(self):
  449. return self._exciterGain
  450. def setExciterEnabled(self, enable):
  451. self._exciterEnable = enable
  452. def isExciterEnabled(self):
  453. return self._exciterEnable
  454. def setTestSignalFrequency(self, frequency):
  455. self._testSigFreq = frequency
  456. def getTestSignalFrequency(self):
  457. return self._testSigFreq
  458. def getNextUpdatePacket(self):
  459. if self._exciterFreq != self._oldExciterFreq:
  460. data = self._freqToPhaseInc(self._exciterFreq)
  461. self._oldExciterFreq = self._exciterFreq
  462. self._oldMixerFreq = self._exciterFreq
  463. print('Setting exciter and mixer channel %d frequency to %f Hz' % (self._index, self._phaseIncToFreq(data)))
  464. return (GOALREF_MEMORY['exciter']['phaseInc'] + self._index, data)
  465. if self._mixerFreq != self._oldMixerFreq:
  466. data = self._freqToPhaseInc(self._mixerFreq)
  467. self._oldMixerFreq = self._mixerFreq
  468. print('Setting mixer channel %d frequency to %f Hz' % (self._index, self._phaseIncToFreq(data)))
  469. return (GOALREF_MEMORY['mixer']['phaseInc'] + self._index, data)
  470. if self._exciterGain != self._oldExciterGain:
  471. data = self._exciterGain
  472. self._oldExciterGain = self._exciterGain
  473. print('Setting exciter channel %d gain to %d' % (self._index, data))
  474. return (GOALREF_MEMORY['exciter']['gain'] + self._index, data)
  475. if self._exciterEnable != self._oldExciterEnable:
  476. data = 1 if self._exciterEnable else 0
  477. self._oldExciterEnable = self._exciterEnable
  478. print('Setting exciter channel %d to %s' % (self._index, 'ENABLED' if data == 1 else 'DISABLED'))
  479. return (GOALREF_MEMORY['exciter']['enable'] + self._index, data)
  480. if self._testSigFreq != self._oldTestSigFreq:
  481. data = self._freqToPhaseInc(self._testSigFreq)
  482. self._oldTestSigFreq = self._testSigFreq
  483. print('Setting test signal channel %d frequency to %f Hz' % (self._index, self._phaseIncToFreq(data)))
  484. return (GOALREF_MEMORY['testSig']['phaseInc'] + self._index, data)
  485. return None
  486. def _freqToPhaseInc(self, freq):
  487. """"!
  488. @brief Helper function to convert from frequency to phase increment used in FPGA
  489. """
  490. return int(round(freq * 2**GOALREF_TABLE_IDX_LEN / GOALREF_MAIN_CLOCK))
  491. def _phaseIncToFreq(self, phaseInc):
  492. """"!
  493. @brief Helper function to convert from phase increment used in FPGA to frequency
  494. """
  495. return GOALREF_MAIN_CLOCK * phaseInc / 2**GOALREF_TABLE_IDX_LEN
  496. def _calculateGain(self, current, frequency):
  497. """!
  498. @brief Helper function to calculate exciter gain from target current and frequency.
  499. Intern calculation:
  500. * Impedance: \f$ Z = \sqrt{R^2 + (2 \cdot L \cdot \pi \cdot f)^2} \f$
  501. * Filter compensation: \f$ filterCompensation = 0.0419 (\frac{f}{1000})^2 - 7.3564 (\frac {f}{1000}) + 586.38 \f$
  502. * Gain: \f$ gain = int(floor(filterCompensation \cdot Z \cdot I \cdot loadfactor))\f$
  503. @param current Exciter current which is set through the user.
  504. @param frequency
  505. @return gain 0-65535 (0-5V)
  506. """
  507. # if True:
  508. # # calculates gain for the DAC input value, feeding the Amplifier, drive the Exciter Current
  509. # # f is the frequency of the exciter current
  510. # # Ll is the inductance of the exciter
  511. # # Rl is the resistance of the exciter and other losses (estimated to reduce resonant peak)
  512. # # Iout is the desired output current in Ampere_Peak_Peak
  513. # C1 = 3e-6
  514. # L1 = 3.3e-6
  515. # C2 = 250e-9
  516. # Rl = self._resistance # 35 for ltc4444-5; 5 for LTC4446
  517. # Vcc = 24
  518. # Vlogic = 5
  519. # Iout = current
  520. # f = frequency
  521. # Ll = self._inductance + 6e-6
  522. #
  523. # w = 2 * cmath.pi * f
  524. #
  525. # Zin = 2.0 * (1 / (1j * w * C1) + 1j * w * L1) + 1 / (
  526. # 1 / (1j * w * Ll + Rl) + 1j * w * C2) # input resistance off outputfilter
  527. #
  528. # gm = 1 / Zin * (1 / (1j * w * C2)) / (1 / (1j * w * C2) + 1j * w * Ll + Rl) # [gm]=[Iout]/[Uin] = A/V
  529. # gm *= 1.72 * Vcc / Vlogic # 1.72 = correction because of Dead Time in PWM * 2 (Full Bridge)
  530. #
  531. # Uin = Iout / gm
  532. #
  533. # gain = 65536 * Uin / Vlogic
  534. # gain *= self._load_factor
  535. #
  536. # return 1200
  537. # else:
  538. # Absolute value of impedance
  539. impedance = np.sqrt(self._resistance ** 2 + (self._inductance * 2 * np.pi * frequency) ** 2)
  540. # Compensation function for amplifier input filter empirically fitted from measurement data
  541. filterCompensation = .0419 * (frequency / 1000.0) ** 2 - 7.3564 * (frequency / 1000.0) + 586.38
  542. gain = int(np.floor(filterCompensation * impedance * current * self._load_factor))
  543. return gain