main.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. # -*- coding: utf-8 -*-
  2. #
  3. # AWL simulator - PiXtend hardware interface
  4. #
  5. # Copyright 2018 Michael Buesch <m@bues.ch>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. from __future__ import division, absolute_import, print_function, unicode_literals
  22. #from awlsim.common.cython_support cimport * #@cy
  23. from awlsim.common.compat import *
  24. from awlsim.common.util import *
  25. from awlsim.common.enumeration import *
  26. from awlsim.common.exceptions import *
  27. from awlsim.common.datatypehelpers import * #+cimport
  28. from awlsim.core.hardware_params import *
  29. from awlsim.core.hardware import * #+cimport
  30. from awlsim.core.operators import * #+cimport
  31. from awlsim.core.operatortypes import * #+cimport
  32. from awlsim.core.offset import * #+cimport
  33. from awlsim.core.cpu import * #+cimport
  34. #from awlsimhw_pixtend.main cimport * #@cy
  35. from awlsimhw_pixtend.ppl_shim import * #+cimport
  36. import time
  37. __all__ = [
  38. "HardwareInterface",
  39. ]
  40. class HwParamDesc_boardType(HwParamDesc_str):
  41. typeStr = "BoardType"
  42. EnumGen.start
  43. BOARD_AUTO = EnumGen.item
  44. BOARD_V1_X = EnumGen.item
  45. BOARD_V2_X = EnumGen.item
  46. EnumGen.end
  47. str2type = {
  48. "auto" : BOARD_AUTO,
  49. "v1.x" : BOARD_V1_X,
  50. "v1.2" : BOARD_V1_X,
  51. "v1.3" : BOARD_V1_X,
  52. "v2.x" : BOARD_V2_X,
  53. "v2.0" : BOARD_V2_X,
  54. "v2.1" : BOARD_V2_X,
  55. }
  56. type2str = {
  57. BOARD_AUTO : "auto",
  58. BOARD_V1_X : "v1.x",
  59. BOARD_V2_X : "v2.x",
  60. }
  61. def __init__(self, name, defaultValue, description, **kwargs):
  62. HwParamDesc_str.__init__(self,
  63. name=name,
  64. defaultValue=None,
  65. description=description,
  66. **kwargs)
  67. self.defaultValue = defaultValue
  68. self.defaultValueStr = self.type2str[defaultValue]
  69. def parse(self, value):
  70. lowerValue = value.lower().strip()
  71. if not lowerValue:
  72. return self.defaultValue
  73. try:
  74. return self.str2type[lowerValue]
  75. except KeyError as e:
  76. pass
  77. raise self.ParseError("Invalid board type '%s'. "
  78. "A valid boardType can be either %s." % (
  79. value, listToHumanStr(sorted(dictKeys(self.str2type)))))
  80. class HwParamDesc_pwmMode(HwParamDesc_str):
  81. typeStr = "pwmMode"
  82. EnumGen.start
  83. MODE_SERVO = EnumGen.item
  84. MODE_DUTYCYCLE = EnumGen.item
  85. EnumGen.end
  86. type2str = {
  87. MODE_SERVO : "servo",
  88. MODE_DUTYCYCLE : "dutycycle",
  89. }
  90. str2type = pivotDict(type2str)
  91. def __init__(self, name, defaultValue, description, **kwargs):
  92. HwParamDesc_str.__init__(self,
  93. name=name,
  94. defaultValue=None,
  95. description=description,
  96. **kwargs)
  97. self.defaultValue = defaultValue
  98. self.defaultValueStr = self.type2str[defaultValue]
  99. def parse(self, value):
  100. lowerValue = value.lower().strip()
  101. if not lowerValue:
  102. return self.defaultValue
  103. lowerValue = lowerValue.replace("-", "").replace("_", "")
  104. try:
  105. return self.str2type[lowerValue]
  106. except KeyError as e:
  107. pass
  108. raise self.ParseError("Invalid PWM mode '%s'. "
  109. "A valid pwmMode can be either %s." % (
  110. value, listToHumanStr(sorted(dictKeys(self.str2type)))))
  111. class HwParamDesc_gpioMode(HwParamDesc_str):
  112. typeStr = "GPIO-mode"
  113. EnumGen.start
  114. MODE_GPIO = EnumGen.item
  115. MODE_DHT11 = EnumGen.item
  116. MODE_DHT22 = EnumGen.item
  117. EnumGen.end
  118. type2str = {
  119. MODE_GPIO : "GPIO",
  120. MODE_DHT11 : "DHT11",
  121. MODE_DHT22 : "DHT22",
  122. }
  123. str2type = pivotDict(type2str)
  124. def __init__(self, name, defaultValue, description, **kwargs):
  125. HwParamDesc_str.__init__(self,
  126. name=name,
  127. defaultValue=None,
  128. description=description,
  129. **kwargs)
  130. self.defaultValue = defaultValue
  131. self.defaultValueStr = self.type2str[defaultValue]
  132. def parse(self, value):
  133. upperValue = value.upper().strip()
  134. if not upperValue:
  135. return self.defaultValue
  136. upperValue = upperValue.replace("-", "").replace("_", "")
  137. try:
  138. return self.str2type[upperValue]
  139. except KeyError as e:
  140. pass
  141. raise self.ParseError("Invalid GPIO mode '%s'. "
  142. "A valid gpioMode can be either %s." % (
  143. value, listToHumanStr(sorted(dictKeys(self.str2type)))))
  144. class HardwareInterface_PiXtend(AbstractHardwareInterface): #+cdef
  145. name = "PiXtend"
  146. description = "PiXtend V1.x and V2.x "\
  147. "extension board support.\n"\
  148. "https://www.pixtend.de/"
  149. #TODO watchdog
  150. NR_RELAYS = 4
  151. NR_DO_V1 = 6
  152. NR_DO_V2 = 4
  153. NR_DI = 8
  154. NR_GPIO = 4
  155. NR_AO = 2
  156. NR_AI_V1 = 4
  157. NR_AI_V2 = 2
  158. NR_PWM0 = 2
  159. NR_PWM1 = 2
  160. paramDescs = [
  161. HwParamDesc_boardType("boardType",
  162. defaultValue=HwParamDesc_boardType.BOARD_AUTO,
  163. description="PiXtend board type. This can be either %s." % (
  164. listToHumanStr(sorted(dictKeys(HwParamDesc_boardType.str2type))))),
  165. HwParamDesc_float("pollIntMs",
  166. defaultValue=100.0,
  167. minValue=2.5,
  168. maxValue=10000.0,
  169. description="PiXtend auto-mode poll interval time, in milliseconds"),
  170. HwParamDesc_bool("rs485",
  171. defaultValue=False,
  172. description="Enable RS485 mode. (PiXtend v1.x only)\n"
  173. "If set to True the RS485 output is enabled "
  174. "and the RS232 output is disabled.\n"
  175. "If set to False the RS485 output is disabled "
  176. "and the RS232 output is enabled."),
  177. HwParamDesc_bool("testMode",
  178. defaultValue=False,
  179. description="Enable testing mode. DO NOT USE THIS OPTION!",
  180. hidden=True),
  181. ]
  182. for i in range(NR_RELAYS):
  183. paramDescs.append(HwParamDesc_oper(
  184. "relay%d_addr" % i,
  185. allowedOperTypes=(AwlOperatorTypes.MEM_A,),
  186. allowedOperWidths=(1,),
  187. description="Relay output %d address" % i))
  188. for i in range(max(NR_DO_V1, NR_DO_V2)):
  189. paramDescs.append(HwParamDesc_oper(
  190. "digitalOut%d_addr" % i,
  191. allowedOperTypes=(AwlOperatorTypes.MEM_A,),
  192. allowedOperWidths=(1,),
  193. description="Digital output %d address" % i))
  194. for i in range(NR_DI):
  195. paramDescs.append(HwParamDesc_oper(
  196. "digitalIn%d_addr" % i,
  197. allowedOperTypes=(AwlOperatorTypes.MEM_E,),
  198. allowedOperWidths=(1,),
  199. description="Digital input %d address" % i))
  200. for i in range(NR_GPIO):
  201. paramDescs.append(HwParamDesc_oper(
  202. "gpio%d_addr" % i,
  203. allowedOperTypes=(AwlOperatorTypes.MEM_E,
  204. AwlOperatorTypes.MEM_A,),
  205. allowedOperWidths=(1,),
  206. description="GPIO %d bit address (can be input (I/E) or output (Q/A))" % i))
  207. paramDescs.append(HwParamDesc_oper(
  208. "gpio%d_temp_addr" % i,
  209. allowedOperTypes=(AwlOperatorTypes.MEM_E,),
  210. allowedOperWidths=(16,),
  211. description="DHT11/DHT22 on GPIO %d temperature sensor input address" % i))
  212. paramDescs.append(HwParamDesc_oper(
  213. "gpio%d_hum_addr" % i,
  214. allowedOperTypes=(AwlOperatorTypes.MEM_E,),
  215. allowedOperWidths=(16,),
  216. description="DHT11/DHT22 on GPIO %d humidity sensor input address" % i))
  217. paramDescs.append(HwParamDesc_gpioMode(
  218. "gpio%d_mode" % i,
  219. defaultValue=HwParamDesc_gpioMode.MODE_GPIO,
  220. description="GPIO %d operation mode."
  221. "Possible values: %s" % (i,
  222. listToHumanStr(sorted(dictKeys(HwParamDesc_gpioMode.str2type))))))
  223. paramDescs.append(HwParamDesc_bool(
  224. "gpio%d_pullup" % i,
  225. defaultValue=False,
  226. description="Enable the pull-up resistors for GPIO input %d" % i))
  227. for i in range(NR_AO):
  228. paramDescs.append(HwParamDesc_oper(
  229. "analogOut%d_addr" % i,
  230. allowedOperTypes=(AwlOperatorTypes.MEM_A,),
  231. allowedOperWidths=(16,),
  232. description="Analog output (DAC) %d address" % i))
  233. for i in range(max(NR_AI_V1, NR_AI_V2)):
  234. paramDescs.append(HwParamDesc_oper(
  235. "analogIn%d_addr" % i,
  236. allowedOperTypes=(AwlOperatorTypes.MEM_E,),
  237. allowedOperWidths=(16,),
  238. description="Analog input %d word address" % i))
  239. if i <= 1:
  240. paramDescs.append(HwParamDesc_bool(
  241. "analogIn%d_10V" % i,
  242. defaultValue=True,
  243. description="TRUE: Use 10 volts input. FALSE: Use 5 volts input"))
  244. paramDescs.append(HwParamDesc_int(
  245. "analogIn%d_nos" % i,
  246. defaultValue=10,
  247. minValue=1,
  248. maxValue=50,
  249. description="Number of samples for analog input (1, 5, 10 or 50)"))
  250. paramDescs.append(HwParamDesc_int(
  251. "analogIn_kHz",
  252. defaultValue=125,
  253. minValue=125,
  254. maxValue=8000,
  255. description="Analog sampling frequency in kHz. Default: 125 kHz."))
  256. for i in range(NR_PWM0):
  257. __name = "AB"[i]
  258. paramDescs.append(HwParamDesc_oper(
  259. "pwm0%s_addr" % __name,
  260. allowedOperTypes=(AwlOperatorTypes.MEM_A,),
  261. allowedOperWidths=(16,),
  262. description="Output word address of 16 bit PWM "
  263. "(Pulse Width Modulation) module 0%s" % __name))
  264. paramDescs.append(HwParamDesc_oper(
  265. "pwm%d_addr" % i,
  266. allowedOperTypes=(AwlOperatorTypes.MEM_A,),
  267. allowedOperWidths=(16,),
  268. deprecated=True,
  269. compatReplacement=("pwm0%s_addr" % __name)))
  270. paramDescs.append(HwParamDesc_bool(
  271. "pwm%d_servoOverDrive" % i,
  272. deprecated=True))
  273. paramDescs.append(HwParamDesc_pwmMode(
  274. "pwm0_mode",
  275. defaultValue=HwParamDesc_pwmMode.MODE_DUTYCYCLE,
  276. description="Set PWM0 operation mode. "
  277. "Possible values: %s" % (
  278. listToHumanStr(sorted(dictKeys(HwParamDesc_pwmMode.str2type))))))
  279. paramDescs.append(HwParamDesc_bool(
  280. "pwm_servoMode",
  281. defaultValue=False,
  282. deprecated=True,
  283. replacement="pwm0_mode"))
  284. paramDescs.append(HwParamDesc_int(
  285. "pwm0_baseFreqHz",
  286. defaultValue=0,
  287. minValue=0,
  288. maxValue=16000000,
  289. description="PWM0 base frequency, in Hz. "
  290. "Possible values: 16000000, 2000000, 250000, 62500, 15625, 0. "
  291. "Set to 0 to disable PWM0."))
  292. paramDescs.append(HwParamDesc_oper(
  293. "pwm0_period",
  294. allowedOperTypes=(AwlOperatorTypes.MEM_A,
  295. AwlOperatorTypes.IMM),
  296. description="PWM0 period.\n"
  297. "This can either be an integer between 0 and L#65000.\n"
  298. "Or an output word (AW, QW) where the program "
  299. "writes the desired period to.\n"
  300. "Defaults to L#65000, if not specified."))
  301. paramDescs.append(HwParamDesc_int(
  302. "pwm_baseFreqHz",
  303. defaultValue=0,
  304. minValue=0,
  305. maxValue=16000000,
  306. deprecated=True,
  307. compatReplacement="pwm0_baseFreqHz"))
  308. paramDescs.append(HwParamDesc_oper(
  309. "pwm_period",
  310. allowedOperTypes=(AwlOperatorTypes.MEM_A,
  311. AwlOperatorTypes.IMM),
  312. deprecated=True,
  313. compatReplacement="pwm0_period"))
  314. for i in range(NR_PWM1):
  315. __name = "AB"[i]
  316. paramDescs.append(HwParamDesc_oper(
  317. "pwm1%s_addr" % __name,
  318. allowedOperTypes=(AwlOperatorTypes.MEM_A,),
  319. allowedOperWidths=(16,),
  320. description="Output word address of 8 bit PWM "
  321. "(Pulse Width Modulation) module 1%s" % __name))
  322. paramDescs.append(HwParamDesc_pwmMode(
  323. "pwm1_mode",
  324. defaultValue=HwParamDesc_pwmMode.MODE_DUTYCYCLE,
  325. description="Set PWM1 operation mode. "
  326. "Possible values: %s" % (
  327. listToHumanStr(sorted(dictKeys(HwParamDesc_pwmMode.str2type))))))
  328. paramDescs.append(HwParamDesc_int(
  329. "pwm1_baseFreqHz",
  330. defaultValue=0,
  331. minValue=0,
  332. maxValue=16000000,
  333. description="PWM1 base frequency, in Hz. "
  334. "Possible values: 16000000, 2000000, 500000, "
  335. "250000, 125000, 62500, 15625, 0. "
  336. "Set to 0 to disable PWM1."))
  337. paramDescs.append(HwParamDesc_oper(
  338. "pwm1_period",
  339. allowedOperTypes=(AwlOperatorTypes.MEM_A,
  340. AwlOperatorTypes.IMM),
  341. description="PWM1 period.\n"
  342. "This can either be an integer between 0 and 255.\n"
  343. "Or an output word (AW, QW) where the program "
  344. "writes the desired period to.\n"
  345. "Defaults to 255, if not specified."))
  346. def __init__(self, sim, parameters={}):
  347. AbstractHardwareInterface.__init__(self,
  348. sim = sim,
  349. parameters = parameters)
  350. self.__pixtendInitialized = False
  351. self.__pixtend = None
  352. self.__haveInputData = False
  353. def __build(self):
  354. # Build all Relay() objects
  355. self.__relays = []
  356. for i in range(self.NR_RELAYS):
  357. oper = self.getParamValueByName("relay%d_addr" % i)
  358. if oper is None:
  359. continue
  360. bitOffset = oper.offset.toLongBitOffset()
  361. r = Relay(self.__pixtend, self.__isV2, i, bitOffset,
  362. not self.isInProcessImage(oper.offset, 1, True))
  363. self.__relays.append(r)
  364. # Build all DigitalOut() objects
  365. self.__DOs = []
  366. for i in range(self.NR_DO_V2 if self.__isV2 else self.NR_DO_V1):
  367. oper = self.getParamValueByName("digitalOut%d_addr" % i)
  368. if oper is None:
  369. continue
  370. bitOffset = oper.offset.toLongBitOffset()
  371. directOnly = not self.isInProcessImage(oper.offset, 1, True)
  372. do = DigitalOut(self.__pixtend, self.__isV2, i, bitOffset, directOnly)
  373. self.__DOs.append(do)
  374. # Build all DigitalIn() objects
  375. self.__DIs = []
  376. for i in range(self.NR_DI):
  377. oper = self.getParamValueByName("digitalIn%d_addr" % i)
  378. if oper is None:
  379. continue
  380. bitOffset = oper.offset.toLongBitOffset()
  381. directOnly = not self.isInProcessImage(oper.offset, 1, False)
  382. di = DigitalIn(self.__pixtend, self.__isV2, i, bitOffset, directOnly)
  383. self.__DIs.append(di)
  384. # Build all GPIO output objects
  385. self.__GPIO_out = []
  386. for i in range(self.NR_GPIO):
  387. mode = self.getParamValueByName("gpio%d_mode" % i)
  388. if mode != HwParamDesc_gpioMode.MODE_GPIO:
  389. continue
  390. oper = self.getParamValueByName("gpio%d_addr" % i)
  391. if oper is None:
  392. continue
  393. if oper.operType != AwlOperatorTypes.MEM_A:
  394. continue
  395. bitOffset = oper.offset.toLongBitOffset()
  396. gpio = GPIO(self.__pixtend, self.__isV2, i, bitOffset,
  397. not self.isInProcessImage(oper.offset, 1, True))
  398. gpio.mode = GPIO.MODE_OUTPUT
  399. self.__GPIO_out.append(gpio)
  400. # Build all GPIO input objects
  401. self.__GPIO_in = []
  402. for i in range(self.NR_GPIO):
  403. mode = self.getParamValueByName("gpio%d_mode" % i)
  404. if mode != HwParamDesc_gpioMode.MODE_GPIO:
  405. continue
  406. oper = self.getParamValueByName("gpio%d_addr" % i)
  407. if oper is None:
  408. continue
  409. if oper.operType != AwlOperatorTypes.MEM_E:
  410. continue
  411. bitOffset = oper.offset.toLongBitOffset()
  412. gpio = GPIO(self.__pixtend, self.__isV2, i, bitOffset,
  413. not self.isInProcessImage(oper.offset, 1, False))
  414. gpio.mode = GPIO.MODE_INPUT
  415. gpio.pullUp = self.getParamValueByName("gpio%d_pullup" % i)
  416. self.__GPIO_in.append(gpio)
  417. GPIO.setGlobalPullUpEnable(self.__pixtend, self.__isV2,
  418. any(gpio.pullUp for gpio in self.__GPIO_in))
  419. # Build all DHT11/DHT22 input objects
  420. self.__temps = []
  421. self.__hums = []
  422. for i in range(self.NR_GPIO):
  423. mode = self.getParamValueByName("gpio%d_mode" % i)
  424. if mode not in {HwParamDesc_gpioMode.MODE_DHT11,
  425. HwParamDesc_gpioMode.MODE_DHT22}:
  426. continue
  427. sensorType = {
  428. HwParamDesc_gpioMode.MODE_DHT11 : EnvSensorBase.TYPE_DHT11,
  429. HwParamDesc_gpioMode.MODE_DHT22 : EnvSensorBase.TYPE_DHT22,
  430. }[mode]
  431. tempOper = self.getParamValueByName("gpio%d_temp_addr" % i)
  432. humOper = self.getParamValueByName("gpio%d_hum_addr" % i)
  433. if tempOper is not None:
  434. bitOffset = tempOper.offset.toLongBitOffset()
  435. temp = TempIn(sensorType,
  436. self.__pixtend, self.__isV2, i, bitOffset,
  437. not self.isInProcessImage(tempOper.offset, 16, False))
  438. self.__temps.append(temp)
  439. if humOper is not None:
  440. bitOffset = humOper.offset.toLongBitOffset()
  441. hum = HumIn(sensorType,
  442. self.__pixtend, self.__isV2, i, bitOffset,
  443. not self.isInProcessImage(humOper.offset, 16, False))
  444. self.__hums.append(hum)
  445. # Build all analog input objects
  446. self.__AIs = []
  447. for i in range(self.NR_AI_V2 if self.__isV2 else self.NR_AI_V1):
  448. oper = self.getParamValueByName("analogIn%d_addr" % i)
  449. if oper is None:
  450. continue
  451. bitOffset = oper.offset.toLongBitOffset()
  452. ai = AnalogIn(self.__pixtend, self.__isV2, i, bitOffset,
  453. not self.isInProcessImage(oper.offset, 16, False))
  454. self.__AIs.append(ai)
  455. if i <= 1:
  456. ai.jumper10V = self.getParamValueByName("analogIn%d_10V" % i)
  457. ai.numberOfSamples = self.getParamValueByName("analogIn%d_nos" % i)
  458. # Build all analog output objects
  459. self.__AOs = []
  460. for i in range(self.NR_AO):
  461. oper = self.getParamValueByName("analogOut%d_addr" % i)
  462. if oper is None:
  463. continue
  464. bitOffset = oper.offset.toLongBitOffset()
  465. ao = AnalogOut(self.__pixtend, self.__isV2, i, bitOffset,
  466. not self.isInProcessImage(oper.offset, 16, True))
  467. self.__AOs.append(ao)
  468. # Build all PWM() objects for PWM0
  469. self.__PWM0s = []
  470. for i in range(self.NR_PWM0):
  471. pwmName = "AB"[i]
  472. oper = self.getParamValueByName("pwm%d_addr" % i,
  473. fallbackToDefault=False)
  474. if oper is None:
  475. oper = self.getParamValueByName("pwm0%s_addr" % pwmName)
  476. if oper is None:
  477. continue
  478. bitOffset = oper.offset.toLongBitOffset()
  479. pwm = PWM0(self.__pixtend, self.__isV2, i, bitOffset,
  480. not self.isInProcessImage(oper.offset, 16, True))
  481. self.__PWM0s.append(pwm)
  482. pwm.enabled = True
  483. servoMode = self.getParamValueByName("pwm_servoMode",
  484. fallbackToDefault=False)
  485. if servoMode is None:
  486. mode = self.getParamValueByName("pwm0_mode")
  487. servoMode = (mode == HwParamDesc_pwmMode.MODE_SERVO)
  488. pwm.servoMode = servoMode
  489. # Handle pwm0_period parameter.
  490. try:
  491. maxPeriod = 65535 if self.__isV2 else 65000
  492. oper = self.getParamValueByName("pwm_period")
  493. if not oper:
  494. oper = self.getParamValueByName("pwm0_period")
  495. if not oper:
  496. # Use default constant pwm0_period
  497. PWM0Period(self.__pixtend, self.__isV2, 0, 0).setPWMPeriod(maxPeriod)
  498. else:
  499. if oper.operType == AwlOperatorTypes.IMM:
  500. # Use custom constant pwm0_period
  501. period = oper.immediate
  502. if period < 0 or period > maxPeriod:
  503. raise ValueError
  504. PWM0Period(self.__pixtend, self.__isV2, 0, 0).setPWMPeriod(period)
  505. elif oper.operType == AwlOperatorTypes.MEM_A and\
  506. oper.width == 16:
  507. # Use custom dynamic pwm0_period
  508. bitOffset = oper.offset.toLongBitOffset()
  509. pwm = PWM0Period(self.__pixtend, self.__isV2, 0, bitOffset,
  510. not self.isInProcessImage(oper.offset, 16, True))
  511. self.__PWM0s.append(pwm)
  512. pwm.setPWMPeriod(0)
  513. else:
  514. raise ValueError
  515. except ValueError as e:
  516. self.raiseException("Unsupported 'pwm0_period' parameter value.")
  517. # Build all PWM() objects for PWM1
  518. self.__PWM1s = []
  519. if self.__isV2:
  520. for i in range(self.NR_PWM1):
  521. pwmName = "AB"[i]
  522. oper = self.getParamValueByName("pwm1%s_addr" % pwmName)
  523. if oper is None:
  524. continue
  525. bitOffset = oper.offset.toLongBitOffset()
  526. pwm = PWM1(self.__pixtend, self.__isV2, i, bitOffset,
  527. not self.isInProcessImage(oper.offset, 16, True))
  528. self.__PWM1s.append(pwm)
  529. pwm.enabled = True
  530. mode = self.getParamValueByName("pwm1_mode")
  531. pwm.servoMode = (mode == HwParamDesc_pwmMode.MODE_SERVO)
  532. # Handle pwm1_period parameter.
  533. try:
  534. maxPeriod = 255
  535. oper = self.getParamValueByName("pwm1_period")
  536. if not oper:
  537. # Use default constant pwm1_period
  538. PWM1Period(self.__pixtend, self.__isV2, 0, 0).setPWMPeriod(maxPeriod)
  539. else:
  540. if oper.operType == AwlOperatorTypes.IMM:
  541. # Use custom constant pwm1_period
  542. period = oper.immediate
  543. if period < 0 or period > maxPeriod:
  544. raise ValueError
  545. PWM1Period(self.__pixtend, self.__isV2, 0, 0).setPWMPeriod(period)
  546. elif oper.operType == AwlOperatorTypes.MEM_A and\
  547. oper.width == 16:
  548. # Use custom dynamic pwm1_period
  549. bitOffset = oper.offset.toLongBitOffset()
  550. pwm = PWM1Period(self.__pixtend, self.__isV2, 0, bitOffset,
  551. not self.isInProcessImage(oper.offset, 16, True))
  552. self.__PWM1s.append(pwm)
  553. pwm.setPWMPeriod(0)
  554. else:
  555. raise ValueError
  556. except ValueError as e:
  557. self.raiseException("Unsupported 'pwm1_period' parameter value.")
  558. # Build a list of all outputs
  559. self.__allOutputs = []
  560. self.__allOutputs.extend(self.__relays)
  561. self.__allOutputs.extend(self.__DOs)
  562. self.__allOutputs.extend(self.__GPIO_out)
  563. self.__allOutputs.extend(self.__AOs)
  564. self.__allOutputs.extend(self.__PWM0s)
  565. self.__allOutputs.extend(self.__PWM1s)
  566. # Build a list of all inputs
  567. self.__allInputs = []
  568. self.__allInputs.extend(self.__DIs)
  569. self.__allInputs.extend(self.__GPIO_in)
  570. self.__allInputs.extend(self.__temps)
  571. self.__allInputs.extend(self.__hums)
  572. self.__allInputs.extend(self.__AIs)
  573. # Build a list of all process image accessible outputs.
  574. self.__allProcOutputs = [ o for o in self.__allOutputs if not o.directOnly ]
  575. # Build a list of all process image accessible inputs.
  576. self.__allProcInputs = [ i for i in self.__allInputs if not i.directOnly ]
  577. def calcFirstLastByte(IOs):
  578. first = last = None
  579. for io in IOs:
  580. beginByteOffset = ((io.byteOffset * 8) + io.bitOffset) // 8
  581. endByteOffset = beginByteOffset + io.byteSize - 1
  582. if first is None or beginByteOffset < first:
  583. first = beginByteOffset
  584. if last is None or endByteOffset > last:
  585. last = endByteOffset
  586. return first, last
  587. # Find the offsets of the first and the last output byte
  588. firstOutByte, lastOutByte = calcFirstLastByte(self.__allProcOutputs)
  589. firstInByte, lastInByte = calcFirstLastByte(self.__allProcInputs)
  590. # Build dicts to map from byteOffset to I/O wrapper.
  591. self.__byteOffsetToInput = {
  592. inp.byteOffset : inp
  593. for inp in self.__allInputs
  594. }
  595. self.__byteOffsetToOutput = {
  596. out.byteOffset : out
  597. for out in self.__allOutputs
  598. }
  599. # Store the output base and size
  600. if firstOutByte is None or lastOutByte is None:
  601. self.__outBase = 0
  602. self.__outSize = 0
  603. else:
  604. self.__outBase = self.outputAddressBase + firstOutByte
  605. self.__outSize = lastOutByte - firstOutByte + 1
  606. # Setup all outputs
  607. for out in itertools.chain(self.__relays,
  608. self.__DOs,
  609. self.__GPIO_out,
  610. self.__AOs,
  611. self.__PWM0s,
  612. self.__PWM1s):
  613. out.setup(-firstOutByte)
  614. # Store the input base and size
  615. if firstInByte is None or lastInByte is None:
  616. self.__inBase = 0
  617. self.__inSize = 0
  618. else:
  619. self.__inBase = self.inputAddressBase + firstInByte
  620. self.__inSize = lastInByte - firstInByte + 1
  621. # Setup all inputs
  622. for inp in itertools.chain(self.__DIs,
  623. self.__GPIO_in,
  624. self.__temps,
  625. self.__hums,
  626. self.__AIs):
  627. inp.setup(-firstInByte)
  628. if not self.__isV2:
  629. # Configure RS232/RS485
  630. try:
  631. rs485 = self.getParamValueByName("rs485")
  632. if rs485:
  633. self.__pixtend.serial_mode = self.__pixtend.RS485
  634. else:
  635. self.__pixtend.serial_mode = self.__pixtend.RS232
  636. except Exception as e:
  637. self.raiseException("Failed to set RS232/RS485 mode: %s" % str(e))
  638. if not self.__isV2:
  639. # Configure AnalogOut SPI communication.
  640. try:
  641. if self.__AOs:
  642. self.__pixtend.open_dac()
  643. except (IOError, ValueError) as e:
  644. self.raiseException("Failed to open DAC communication: %s" % str(e))
  645. # Configure global values of AnalogIn.
  646. try:
  647. AnalogIn.setFreq(self.__pixtend, self.__isV2,
  648. self.getParamValueByName("analogIn_kHz"))
  649. except ValueError as e:
  650. self.raiseException("Unsupported 'analogIn_kHz' parameter value. "
  651. "Supported values are: 125, 250, 500, 1000, 4000, 8000.")
  652. # Configure global values of PWM0.
  653. try:
  654. if self.__PWM0s:
  655. PWM0.setServoMode(self.__pixtend, self.__isV2,
  656. self.__PWM0s[0].servoMode)
  657. except ValueError as e:
  658. self.raiseException("Unsupported 'pwm_servoMode' parameter value.")
  659. try:
  660. freqHz = self.getParamValueByName("pwm_baseFreqHz",
  661. fallbackToDefault=False)
  662. if freqHz is None:
  663. freqHz = self.getParamValueByName("pwm0_baseFreqHz")
  664. if not self.__PWM0s:
  665. freqHz = 0
  666. PWM0Period.setBaseFreq(self.__pixtend, self.__isV2, freqHz)
  667. except ValueError as e:
  668. self.raiseException("Unsupported 'pwm0_baseFreqHz' parameter value. "
  669. "Supported values are: 16000000, 2000000, 250000, 62500, 15625, 0.")
  670. if self.__isV2:
  671. # Configure global values of PWM1.
  672. try:
  673. if self.__PWM1s:
  674. PWM1.setServoMode(self.__pixtend,
  675. self.__PWM1s[0].servoMode)
  676. except ValueError as e:
  677. self.raiseException("Unsupported 'pwm1_mode' parameter value.")
  678. try:
  679. freqHz = self.getParamValueByName("pwm1_baseFreqHz")
  680. if not self.__PWM1s:
  681. freqHz = 0
  682. PWM1Period.setBaseFreq(self.__pixtend, freqHz)
  683. except ValueError as e:
  684. self.raiseException("Unsupported 'pwm1_baseFreqHz' parameter value. "
  685. "Supported values are: 16000000, 2000000, "
  686. "500000, 250000, 125000, 62500, 15625, 0.")
  687. def __tryConnect(self, boardType, timeout=5.0):
  688. """Try to connect to the PiXtend board.
  689. """
  690. #@cy cdef double minPollIntV1
  691. #@cy cdef double minPollIntV2
  692. self.__prevSpiCount = 0
  693. # Import the Pixtend library.
  694. if boardType == HwParamDesc_boardType.BOARD_V1_X:
  695. self.__isV2 = False
  696. printDebug("Trying to import PiXtend v1.x library")
  697. try:
  698. from pixtendlib import Pixtend as pixtend_class
  699. except ImportError as e:
  700. self.raiseException("Failed to import pixtendlib.Pixtend module"
  701. ":\n%s" % str(e))
  702. elif boardType == HwParamDesc_boardType.BOARD_V2_X:
  703. self.__isV2 = True
  704. printDebug("Trying to import PiXtend v2.x library")
  705. try:
  706. from pixtendv2s import PiXtendV2S as pixtend_class
  707. except ImportError as e:
  708. self.raiseException("Failed to import pixtendv2s.PiXtendV2S module"
  709. ":\n%s" % str(e))
  710. else:
  711. self.raiseException("Unknown board type.")
  712. self.__pixtend_class = pixtend_class
  713. # Get the configured poll interval
  714. self.__pollInt = float(self.getParamValueByName("pollIntMs")) / 1000.0
  715. minPollIntV1 = 0.025
  716. minPollIntV2 = 0.0025
  717. if not self.__isV2 and\
  718. not pyFloatEqual(self.__pollInt, minPollIntV1) and\
  719. self.__pollInt < minPollIntV1:
  720. self.raiseException("pollIntMs is too low. It must be at least 25 ms.")
  721. if self.__isV2 and\
  722. not pyFloatEqual(self.__pollInt, minPollIntV2) and\
  723. self.__pollInt < minPollIntV2:
  724. self.raiseException("pollIntMs is too low. It must be at least 2.5 ms.")
  725. self.__testMode = self.getParamValueByName("testMode")
  726. if self.__testMode:
  727. # In test mode use poll interval as small as possible.
  728. self.__pollInt = 0.0025 if self.__isV2 else 0.0
  729. # Initialize PiXtend
  730. self.__pixtend = None
  731. try:
  732. printDebug("Trying to connect to PiXtend v%d.x" % (2 if self.__isV2 else 1))
  733. if self.__isV2:
  734. # PiXtend v2.x
  735. self.__pixtend = self.__pixtend_class(
  736. com_interval=self.__pollInt,
  737. model=self.__pixtend_class.PIXTENDV2S_MODEL,
  738. )
  739. initialSpiCount = self.__pixtend.spi_transfers & 0xFFFF
  740. else:
  741. # PiXtend v1.x
  742. self.__pixtend = self.__pixtend_class()
  743. self.__pixtend.open()
  744. # Wait for PiXtend to wake up.
  745. t, tMax = 0, int(round(timeout * 10))
  746. while True:
  747. self.cpu.updateTimestamp()
  748. if self.__isV2:
  749. if self.__pixtendPoll(self.cpu.now):
  750. spiCount = self.__pixtend.spi_transfers & 0xFFFF
  751. if spiCount != initialSpiCount:
  752. break # success
  753. else:
  754. initialSpiCount = self.__pixtend.spi_transfers & 0xFFFF
  755. else:
  756. if self.__pixtendPoll(self.cpu.now):
  757. break # success
  758. t += 1
  759. if t >= tMax:
  760. self.raiseException("Timeout waiting "
  761. "for PiXtend auto-mode.")
  762. time.sleep(0.1)
  763. if self.__isV2 and self.__pixtend.model_in_error:
  764. self.raiseException("Invalid board model number detected")
  765. except Exception as e:
  766. with suppressAllExc:
  767. self.__shutdown()
  768. self.raiseException("Failed to init PiXtend: %s" % (
  769. str(e)))
  770. printInfo("Connected to PiXtend v%d.x" % (2 if self.__isV2 else 1))
  771. def doStartup(self):
  772. if self.__pixtendInitialized:
  773. return
  774. # Connect to the PiXtend board
  775. boardType = self.getParamValueByName("boardType")
  776. if boardType == HwParamDesc_boardType.BOARD_AUTO:
  777. try:
  778. self.__tryConnect(HwParamDesc_boardType.BOARD_V1_X,
  779. timeout=1.0)
  780. except AwlSimError as e:
  781. self.__tryConnect(HwParamDesc_boardType.BOARD_V2_X)
  782. else:
  783. self.__tryConnect(boardType)
  784. # Build the HW shim and configure the hardware
  785. try:
  786. self.__build()
  787. except Exception as e:
  788. with suppressAllExc:
  789. self.__shutdown()
  790. raise e
  791. if self.__isV2:
  792. self.__prevSpiCount = self.__pixtend.spi_transfers & 0xFFFF
  793. self.__haveInputData = False
  794. self.__nextPoll = self.cpu.now + self.__pollInt
  795. self.__pixtendInitialized = True
  796. def __shutdown(self):
  797. if self.__pixtend:
  798. self.__pixtend.close()
  799. self.__pixtend = None
  800. self.__pixtendInitialized = False
  801. def doShutdown(self):
  802. if self.__pixtendInitialized:
  803. self.__shutdown()
  804. def readInputs(self): #+cdef
  805. #@cy cdef S7CPU cpu
  806. #@cy cdef uint32_t size
  807. #@cy cdef bytearray data
  808. #@cy cdef AbstractIO inp
  809. if self.__haveInputData:
  810. self.__haveInputData = False
  811. cpu = self.cpu
  812. size = self.__inSize
  813. if size:
  814. data = cpu.fetchInputRange(self.__inBase, size)
  815. # Handle all process image inputs
  816. for inp in self.__allProcInputs:
  817. inp.get(data)
  818. cpu.storeInputRange(self.__inBase, data)
  819. def writeOutputs(self): #+cdef
  820. #@cy cdef S7CPU cpu
  821. #@cy cdef uint32_t size
  822. #@cy cdef double now
  823. #@cy cdef bytearray data
  824. #@cy cdef AbstractIO out
  825. cpu = self.cpu
  826. # Run one PiXtend poll cycle, if required.
  827. now = cpu.now
  828. if self.__isV2 or now >= self.__nextPoll:
  829. size = self.__outSize
  830. if size:
  831. data = cpu.fetchOutputRange(self.__outBase, size)
  832. # Handle all process image outputs
  833. for out in self.__allProcOutputs:
  834. out.set(data)
  835. if not self.__pixtendPoll(now):
  836. self.raiseException("PiXtend auto_mode() poll failed.")
  837. def __pixtendPoll(self, now): #@nocy
  838. #@cy cdef ExBool_t __pixtendPoll(self, double now):
  839. #@cy cdef uint16_t spiCount
  840. pixtend = self.__pixtend
  841. if self.__isV2:
  842. # Check if we have new data from the poll thread
  843. # In test mode actually wait for the worker thread.
  844. if self.__testMode:
  845. self.__waitV2Transfer(True)
  846. spiCount = pixtend.spi_transfers & 0xFFFF
  847. self.__haveInputData = (spiCount != self.__prevSpiCount)
  848. self.__prevSpiCount = spiCount
  849. # Check for errors from the poll thread
  850. if not pixtend.crc_header_in_error and\
  851. not pixtend.crc_data_in_error and\
  852. ((pixtend.uc_state & 1) != 0):
  853. return True
  854. else:
  855. # Poll PiXtend auto_mode
  856. self.__nextPoll = now + self.__pollInt
  857. if (pixtend.auto_mode() == 0) and\
  858. ((pixtend.uc_status & 1) != 0):
  859. self.__haveInputData = True
  860. return True
  861. # An error occurred.
  862. self.__haveInputData = False
  863. return False
  864. def __waitV2Transfer(self, waitForBegin): #@nocy
  865. #@cy cdef __waitV2Transfer(self, _Bool waitForBegin):
  866. """Wait for the V2 I/O thread to run completely at least once.
  867. """
  868. #@cy cdef S7CPU cpu
  869. #@cy cdef uint16_t spiCount
  870. #@cy cdef uint16_t spiBeginCount
  871. #@cy cdef uint16_t prevSpiCount
  872. #@cy cdef uint16_t prevSpiBeginCount
  873. #@cy cdef double timeout
  874. #@cy cdef _Bool begin
  875. cpu = self.cpu
  876. pixtend = self.__pixtend
  877. timeout = cpu.now + (self.__pollInt * 10000.0)
  878. prevSpiCount = pixtend.spi_transfers & 0xFFFF
  879. prevSpiBeginCount = pixtend.spi_transfers_begin & 0xFFFF
  880. begin = not waitForBegin
  881. while True:
  882. cpu.updateTimestamp()
  883. if cpu.now >= timeout:
  884. self.raiseException("PiXtend poll wait timeout.")
  885. time.sleep(0.001)
  886. spiBeginCount = pixtend.spi_transfers_begin & 0xFFFF
  887. spiCount = pixtend.spi_transfers & 0xFFFF
  888. if begin:
  889. if spiCount != prevSpiCount:
  890. # The I/O thread ran at least once.
  891. break
  892. else:
  893. if spiBeginCount == prevSpiBeginCount:
  894. # The thread did not begin, yet.
  895. prevSpiCount = spiCount
  896. else:
  897. begin = True
  898. def __syncPixtendPoll(self, waitForBegin): #@nocy
  899. #@cy cdef __syncPixtendPoll(self, _Bool waitForBegin):
  900. """Synchronously wait for the data transfer to/from PiXtend.
  901. """
  902. #@cy cdef S7CPU cpu
  903. #@cy cdef uint32_t retries
  904. # Synchronously run one PiXtend poll cycle.
  905. cpu = self.cpu
  906. retries = 0
  907. while True:
  908. cpu.updateTimestamp()
  909. if self.__isV2:
  910. # Wait until the I/O thread did one transfer.
  911. self.__waitV2Transfer(waitForBegin)
  912. else:
  913. # Wait for the next possible poll slot.
  914. waitTime = self.__nextPoll - cpu.now
  915. if waitTime > 0.0:
  916. if waitTime > self.__pollInt * 2.0:
  917. # Wait time is too big.
  918. self.raiseException("PiXtend poll wait timeout.")
  919. time.sleep(waitTime)
  920. cpu.updateTimestamp()
  921. # Poll PiXtend.
  922. if self.__pixtendPoll(cpu.now):
  923. break # Success
  924. retries += 1
  925. if retries >= 3:
  926. self.raiseException("PiXtend auto_mode() poll failed (sync).")
  927. def directReadInput(self, accessWidth, accessOffset): #@nocy
  928. #@cy cdef bytearray directReadInput(self, uint32_t accessWidth, uint32_t accessOffset):
  929. #@cy cdef AbstractIO inp
  930. try:
  931. inp = self.__byteOffsetToInput[accessOffset]
  932. except KeyError as e:
  933. return bytearray()
  934. if accessWidth != inp.bitSize:
  935. self.raiseException("Directly accessing input at I %d.0 "
  936. "with width %d bit, but only %d bit wide "
  937. "accesses are supported." % (
  938. accessOffset, accessWidth, inp.bitSize))
  939. self.__syncPixtendPoll(True)
  940. data = bytearray(accessWidth // 8)
  941. inp.getWithByteOffset(data, 0)
  942. return data
  943. def directWriteOutput(self, accessWidth, accessOffset, data): #@nocy
  944. #@cy cdef ExBool_t directWriteOutput(self, uint32_t accessWidth, uint32_t accessOffset, bytearray data) except ExBool_val:
  945. #@cy cdef AbstractIO out
  946. try:
  947. out = self.__byteOffsetToOutput[accessOffset]
  948. except KeyError as e:
  949. return False
  950. if accessWidth != out.bitSize:
  951. self.raiseException("Directly accessing output at Q %d.0 "
  952. "with width %d bit, but only %d bit wide "
  953. "accesses are supported." % (
  954. accessOffset, accessWidth, out.bitSize))
  955. out.setWithByteOffset(data, 0)
  956. self.__syncPixtendPoll(True)
  957. return True
  958. # Module entry point
  959. HardwareInterface = HardwareInterface_PiXtend