__init__.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import contextlib
  2. import enum
  3. import logging
  4. import math
  5. import typing
  6. import spidev
  7. from cc1101.addresses import (
  8. StrobeAddress,
  9. ConfigurationRegisterAddress,
  10. StatusRegisterAddress,
  11. FIFORegisterAddress,
  12. )
  13. from cc1101.options import SyncMode, ModulationFormat
  14. _LOGGER = logging.getLogger(__name__)
  15. class Pin(enum.Enum):
  16. GDO0 = "GDO0"
  17. class _TransceiveMode(enum.IntEnum):
  18. """
  19. PKTCTRL0.PKT_FORMAT
  20. """
  21. FIFO = 0b00
  22. SYNCHRONOUS_SERIAL = 0b01
  23. RANDOM_TRANSMISSION = 0b10
  24. ASYNCHRONOUS_SERIAL = 0b11
  25. class MainRadioControlStateMachineState(enum.IntEnum):
  26. """
  27. MARCSTATE - Main Radio Control State Machine State
  28. """
  29. # see "Figure 13: Simplified State Diagram"
  30. # and "Figure 25: Complete Radio Control State Diagram"
  31. IDLE = 0x01
  32. STARTCAL = 0x08 # after IDLE
  33. BWBOOST = 0x09 # after STARTCAL
  34. FS_LOCK = 0x0A
  35. TX = 0x13
  36. class CC1101:
  37. # > All transfers on the SPI interface are done
  38. # > most significant bit first.
  39. # > All transactions on the SPI interface start with
  40. # > a header byte containing a R/W bit, a access bit (B),
  41. # > and a 6-bit address (A5 - A0).
  42. # > [...]
  43. # > Table 45: SPI Address Space
  44. _WRITE_SINGLE_BYTE = 0x00
  45. # > Registers with consecutive addresses can be
  46. # > accessed in an efficient way by setting the
  47. # > burst bit (B) in the header byte. The address
  48. # > bits (A5 - A0) set the start address in an
  49. # > internal address counter. This counter is
  50. # > incremented by one each new byte [...]
  51. _WRITE_BURST = 0x40
  52. _READ_SINGLE_BYTE = 0x80
  53. _READ_BURST = 0xC0
  54. # 29.3 Status Register Details
  55. _SUPPORTED_PARTNUM = 0
  56. _SUPPORTED_VERSION = 0x14
  57. _CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ = 26e6
  58. # see "21 Frequency Programming"
  59. # > f_carrier = f_XOSC / 2**16 * (FREQ + CHAN * ((256 + CHANSPC_M) * 2**CHANSPC_E-2))
  60. _FREQUENCY_CONTROL_WORD_HERTZ_FACTOR = _CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ / 2 ** 16
  61. def __init__(self) -> None:
  62. self._spi = spidev.SpiDev()
  63. @staticmethod
  64. def _log_chip_status_byte(chip_status: int) -> None:
  65. # see "10.1 Chip Status Byte" & "Table 23: Status Byte Summary"
  66. _LOGGER.debug(
  67. "chip status byte: CHIP_RDYn=%d STATE=%s FIFO_BYTES_AVAILBLE=%d",
  68. chip_status >> 7,
  69. bin((chip_status >> 4) & 0b111),
  70. chip_status & 0b1111,
  71. )
  72. def _read_single_byte(
  73. self, register: typing.Union[ConfigurationRegisterAddress, FIFORegisterAddress]
  74. ) -> int:
  75. response = self._spi.xfer([register | self._READ_SINGLE_BYTE, 0])
  76. assert len(response) == 2, response
  77. self._log_chip_status_byte(response[0])
  78. return response[1]
  79. def _read_burst(
  80. self,
  81. start_register: typing.Union[ConfigurationRegisterAddress, FIFORegisterAddress],
  82. length: int,
  83. ) -> typing.List[int]:
  84. response = self._spi.xfer([start_register | self._READ_BURST] + [0] * length)
  85. assert len(response) == length + 1, response
  86. self._log_chip_status_byte(response[0])
  87. return response[1:]
  88. def _read_status_register(self, register: StatusRegisterAddress) -> int:
  89. # > For register addresses in the range 0x30-0x3D,
  90. # > the burst bit is used to select between
  91. # > status registers when burst bit is one, and
  92. # > between command strobes when burst bit is
  93. # > zero. [...]
  94. # > Because of this, burst access is not available
  95. # > for status registers and they must be accessed
  96. # > one at a time. The status registers can only be
  97. # > read.
  98. response = self._spi.xfer([register | self._READ_BURST, 0])
  99. assert len(response) == 2, response
  100. self._log_chip_status_byte(response[0])
  101. return response[1]
  102. def _command_strobe(self, register: StrobeAddress) -> None:
  103. # see "10.4 Command Strobes"
  104. _LOGGER.debug("sending command strobe 0x%02x", register)
  105. response = self._spi.xfer([register | self._WRITE_SINGLE_BYTE])
  106. assert len(response) == 1, response
  107. self._log_chip_status_byte(response[0])
  108. def _write_burst(
  109. self,
  110. start_register: typing.Union[ConfigurationRegisterAddress, FIFORegisterAddress],
  111. values: typing.List[int],
  112. ) -> None:
  113. _LOGGER.debug(
  114. "writing burst: start_register=0x%02x values=%s", start_register, values
  115. )
  116. response = self._spi.xfer([start_register | self._WRITE_BURST] + values)
  117. assert len(response) == len(values) + 1, response
  118. self._log_chip_status_byte(response[0])
  119. assert all(v == response[0] for v in response[1:]), response
  120. def _reset(self) -> None:
  121. self._command_strobe(StrobeAddress.SRES)
  122. def _get_symbol_rate_exponent(self) -> int:
  123. """
  124. MDMCFG4.DRATE_E
  125. """
  126. return self._read_single_byte(ConfigurationRegisterAddress.MDMCFG4) & 0b00001111
  127. def _set_symbol_rate_exponent(self, exponent: int):
  128. mdmcfg4 = self._read_single_byte(ConfigurationRegisterAddress.MDMCFG4)
  129. mdmcfg4 &= 0b11110000
  130. mdmcfg4 |= exponent
  131. self._write_burst(
  132. start_register=ConfigurationRegisterAddress.MDMCFG4, values=[mdmcfg4]
  133. )
  134. def _get_symbol_rate_mantissa(self) -> int:
  135. """
  136. MDMCFG3.DRATE_M
  137. """
  138. return self._read_single_byte(ConfigurationRegisterAddress.MDMCFG3)
  139. def _set_symbol_rate_mantissa(self, mantissa: int) -> None:
  140. self._write_burst(
  141. start_register=ConfigurationRegisterAddress.MDMCFG3, values=[mantissa]
  142. )
  143. @classmethod
  144. def _symbol_rate_floating_point_to_real(cls, mantissa: int, exponent: int) -> float:
  145. # see "12 Data Rate Programming"
  146. return (
  147. (256 + mantissa)
  148. * (2 ** exponent)
  149. * cls._CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ
  150. / (2 ** 28)
  151. )
  152. @classmethod
  153. def _symbol_rate_real_to_floating_point(cls, real: float) -> typing.Tuple[int, int]:
  154. # see "12 Data Rate Programming"
  155. assert real > 0, real
  156. exponent = math.floor(
  157. math.log2(real / cls._CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ) + 20
  158. )
  159. mantissa = round(
  160. real * 2 ** 28 / cls._CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ / 2 ** exponent
  161. - 256
  162. )
  163. if mantissa == 256:
  164. exponent += 1
  165. mantissa = 0
  166. assert 0 < exponent <= 2 ** 4, exponent
  167. assert mantissa <= 2 ** 8, mantissa
  168. return mantissa, exponent
  169. def get_symbol_rate_baud(self) -> float:
  170. return self._symbol_rate_floating_point_to_real(
  171. mantissa=self._get_symbol_rate_mantissa(),
  172. exponent=self._get_symbol_rate_exponent(),
  173. )
  174. def set_symbol_rate_baud(self, real: float) -> None:
  175. # > The data rate can be set from 0.6 kBaud to 500 kBaud [...]
  176. mantissa, exponent = self._symbol_rate_real_to_floating_point(real)
  177. self._set_symbol_rate_mantissa(mantissa)
  178. self._set_symbol_rate_exponent(exponent)
  179. def get_modulation_format(self) -> ModulationFormat:
  180. mdmcfg2 = self._read_single_byte(ConfigurationRegisterAddress.MDMCFG2)
  181. return ModulationFormat((mdmcfg2 >> 4) & 0b111)
  182. def _set_modulation_format(self, modulation_format: ModulationFormat) -> None:
  183. mdmcfg2 = self._read_single_byte(ConfigurationRegisterAddress.MDMCFG2)
  184. mdmcfg2 &= ~(modulation_format << 4)
  185. mdmcfg2 |= modulation_format << 4
  186. self._write_burst(ConfigurationRegisterAddress.MDMCFG2, [mdmcfg2])
  187. def enable_manchester_code(self) -> None:
  188. """
  189. MDMCFG2.MANCHESTER_EN
  190. Enable manchester encoding & decoding.
  191. """
  192. mdmcfg2 = self._read_single_byte(ConfigurationRegisterAddress.MDMCFG2)
  193. mdmcfg2 |= 0b1000
  194. self._write_burst(ConfigurationRegisterAddress.MDMCFG2, [mdmcfg2])
  195. def get_sync_mode(self) -> SyncMode:
  196. mdmcfg2 = self._read_single_byte(ConfigurationRegisterAddress.MDMCFG2)
  197. return SyncMode(mdmcfg2 & 0b11)
  198. def set_sync_mode(self, mode: SyncMode) -> None:
  199. """
  200. MDMCFG2.SYNC_MODE
  201. see "14.3 Byte Synchronization"
  202. """
  203. mdmcfg2 = self._read_single_byte(ConfigurationRegisterAddress.MDMCFG2)
  204. mdmcfg2 &= 0b11111100
  205. mdmcfg2 |= mode
  206. self._write_burst(ConfigurationRegisterAddress.MDMCFG2, [mdmcfg2])
  207. def _set_power_amplifier_setting_index(self, setting_index: int) -> None:
  208. """
  209. FREND0.PA_POWER
  210. > This value is an index to the PATABLE,
  211. > which can be programmed with up to 8 different PA settings.
  212. > In OOK/ASK mode, this selects the PATABLE index to use
  213. > when transmitting a '1'.
  214. > PATABLE index zero is used in OOK/ASK when transmitting a '0'.
  215. > The PATABLE settings from index 0 to the PA_POWER value are
  216. > used for > ASK TX shaping, [...]
  217. see "Figure 32: Shaping of ASK Signal"
  218. > If OOK modulation is used, the logic 0 and logic 1 power levels
  219. > shall be programmed to index 0 and 1 respectively.
  220. """
  221. frend0 = self._read_single_byte(ConfigurationRegisterAddress.FREND0)
  222. frend0 &= 0b000
  223. frend0 |= setting_index
  224. self._write_burst(ConfigurationRegisterAddress.FREND0, [setting_index])
  225. def __enter__(self) -> "CC1101":
  226. # https://docs.python.org/3/reference/datamodel.html#object.__enter__
  227. self._spi.open(0, 0)
  228. self._spi.max_speed_hz = 55700 # empirical
  229. self._reset()
  230. partnum = self._read_status_register(StatusRegisterAddress.PARTNUM)
  231. if partnum != self._SUPPORTED_PARTNUM:
  232. raise ValueError(
  233. "unexpected chip part number {} (expected: {})".format(
  234. partnum, self._SUPPORTED_PARTNUM
  235. )
  236. )
  237. version = self._read_status_register(StatusRegisterAddress.VERSION)
  238. if version != self._SUPPORTED_VERSION:
  239. raise ValueError(
  240. "unexpected chip version number {} (expected: {})".format(
  241. version, self._SUPPORTED_VERSION
  242. )
  243. )
  244. # 6:4 MOD_FORMAT: OOK (default: 2-FSK)
  245. self._set_modulation_format(ModulationFormat.ASK_OOK)
  246. self._set_power_amplifier_setting_index(1)
  247. self._disable_data_whitening()
  248. # 7:6 unused
  249. # 5:4 FS_AUTOCAL: calibrate when going from IDLE to RX or TX
  250. # 3:2 PO_TIMEOUT: default
  251. # 1 PIN_CTRL_EN: default
  252. # 0 XOSC_FORCE_ON: default
  253. self._write_burst(ConfigurationRegisterAddress.MCSM0, [0b010100])
  254. marcstate = self.get_main_radio_control_state_machine_state()
  255. if marcstate != MainRadioControlStateMachineState.IDLE:
  256. raise ValueError("expected marcstate idle (actual: {})".format(marcstate))
  257. return self
  258. def __exit__(self, exc_type, exc_value, traceback) -> bool:
  259. # https://docs.python.org/3/reference/datamodel.html#object.__exit__
  260. self._spi.close()
  261. return False
  262. def get_main_radio_control_state_machine_state(
  263. self
  264. ) -> MainRadioControlStateMachineState:
  265. return MainRadioControlStateMachineState(
  266. self._read_status_register(StatusRegisterAddress.MARCSTATE)
  267. )
  268. def get_marc_state(self) -> MainRadioControlStateMachineState:
  269. """
  270. alias for get_main_radio_control_state_machine_state()
  271. """
  272. return self.get_main_radio_control_state_machine_state()
  273. @classmethod
  274. def _frequency_control_word_to_hertz(cls, control_word: typing.List[int]) -> float:
  275. return (
  276. int.from_bytes(control_word, byteorder="big", signed=False)
  277. * cls._FREQUENCY_CONTROL_WORD_HERTZ_FACTOR
  278. )
  279. @classmethod
  280. def _hertz_to_frequency_control_word(cls, hertz: float) -> typing.List[int]:
  281. return list(
  282. round(hertz / cls._FREQUENCY_CONTROL_WORD_HERTZ_FACTOR).to_bytes(
  283. length=3, byteorder="big", signed=False
  284. )
  285. )
  286. def _get_base_frequency_control_word(self) -> typing.List[int]:
  287. # > The base or start frequency is set by the 24 bitfrequency
  288. # > word located in the FREQ2, FREQ1, FREQ0 registers.
  289. return self._read_burst(
  290. start_register=ConfigurationRegisterAddress.FREQ2, length=3
  291. )
  292. def _set_base_frequency_control_word(self, control_word: typing.List[int]) -> None:
  293. self._write_burst(
  294. start_register=ConfigurationRegisterAddress.FREQ2, values=control_word
  295. )
  296. def get_base_frequency_hertz(self) -> float:
  297. return self._frequency_control_word_to_hertz(
  298. self._get_base_frequency_control_word()
  299. )
  300. def set_base_frequency_hertz(self, freq: float) -> None:
  301. self._set_base_frequency_control_word(
  302. self._hertz_to_frequency_control_word(freq)
  303. )
  304. def __str__(self) -> str:
  305. attrs = (
  306. "marcstate={}".format(
  307. self.get_main_radio_control_state_machine_state().name.lower()
  308. ),
  309. "base_frequency={:.2f}MHz".format(
  310. self.get_base_frequency_hertz() / 10 ** 6
  311. ),
  312. "symbol_rate={:.2f}kBaud".format(self.get_symbol_rate_baud() / 1000),
  313. "modulation_format={}".format(self.get_modulation_format().name),
  314. "sync_mode={}".format(self.get_sync_mode().name),
  315. )
  316. return "CC1101({})".format(", ".join(attrs))
  317. def _get_packet_length(self) -> int:
  318. """
  319. packet length in fixed packet length mode,
  320. maximum packet length in variable packet length mode.
  321. """
  322. return self._read_single_byte(ConfigurationRegisterAddress.PKTLEN)
  323. def get_configuration_register_values(
  324. self,
  325. start_register: ConfigurationRegisterAddress = min(
  326. ConfigurationRegisterAddress
  327. ),
  328. end_register: ConfigurationRegisterAddress = max(ConfigurationRegisterAddress),
  329. ) -> typing.Dict[ConfigurationRegisterAddress, int]:
  330. assert start_register <= end_register, (start_register, end_register)
  331. values = self._read_burst(
  332. start_register=start_register, length=end_register - start_register + 1
  333. )
  334. return {
  335. ConfigurationRegisterAddress(start_register + i): v
  336. for i, v in enumerate(values)
  337. }
  338. def _disable_data_whitening(self):
  339. """
  340. PKTCTRL0.WHITE_DATA
  341. see "15.1 Data Whitening"
  342. > By setting PKTCTRL0.WHITE_DATA=1 [default],
  343. > all data, except the preamble and the sync word
  344. > will be XOR-ed with a 9-bit pseudo-random (PN9)
  345. > sequence before being transmitted.
  346. """
  347. pktctrl0 = self._read_single_byte(ConfigurationRegisterAddress.PKTCTRL0)
  348. pktctrl0 &= 0b10111111
  349. self._write_burst(
  350. start_register=ConfigurationRegisterAddress.PKTCTRL0, values=[pktctrl0]
  351. )
  352. def _get_transceive_mode(self) -> _TransceiveMode:
  353. pktctrl0 = self._read_single_byte(ConfigurationRegisterAddress.PKTCTRL0)
  354. return _TransceiveMode((pktctrl0 >> 4) & 0b11)
  355. def _set_transceive_mode(self, mode: _TransceiveMode) -> None:
  356. _LOGGER.info("changing transceive mode to %s", mode.name)
  357. pktctrl0 = self._read_single_byte(ConfigurationRegisterAddress.PKTCTRL0)
  358. pktctrl0 &= ~0b00110000
  359. pktctrl0 |= mode << 4
  360. self._write_burst(
  361. start_register=ConfigurationRegisterAddress.PKTCTRL0, values=[pktctrl0]
  362. )
  363. def _flush_tx_fifo_buffer(self) -> None:
  364. # > Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states.
  365. _LOGGER.debug("flushing tx fifo buffer")
  366. self._command_strobe(StrobeAddress.SFTX)
  367. def transmit(self, payload: bytes) -> None:
  368. # see "15.2 Packet Format"
  369. # > In variable packet length mode, [...]
  370. # > The first byte written to the TXFIFO must be different from 0.
  371. if payload[0] == 0:
  372. raise ValueError(
  373. "in variable packet length mode the first byte of payload must not be null"
  374. + "\npayload: {!r}".format(payload)
  375. )
  376. marcstate = self.get_main_radio_control_state_machine_state()
  377. if marcstate != MainRadioControlStateMachineState.IDLE:
  378. raise Exception(
  379. "device must be idle before transmission (current marcstate: {})".format(
  380. marcstate.name
  381. )
  382. )
  383. max_packet_length = self._get_packet_length()
  384. if len(payload) > max_packet_length:
  385. raise ValueError(
  386. "payload exceeds maximum payload length of {} bytes".format(
  387. max_packet_length
  388. )
  389. + "\npayload: {!r}".format(payload)
  390. )
  391. self._flush_tx_fifo_buffer()
  392. self._write_burst(FIFORegisterAddress.TX, list(payload))
  393. _LOGGER.info(
  394. "transmitting 0x%s (%r)",
  395. "".join("{:02x}".format(b) for b in payload),
  396. payload,
  397. )
  398. self._command_strobe(StrobeAddress.STX)
  399. @contextlib.contextmanager
  400. def asynchronous_transmission(self) -> typing.Iterator[Pin]:
  401. """
  402. see "27.1 Asynchronous Serial Operation"
  403. >>> with cc1101.CC1101() as transceiver:
  404. >>> transceiver.set_base_frequency_hertz(433.92e6)
  405. >>> transceiver.set_symbol_rate_baud(600)
  406. >>> print(transceiver)
  407. >>> with transceiver.asynchronous_transmission():
  408. >>> # send digital signal to GDO0 pin
  409. """
  410. self._set_transceive_mode(_TransceiveMode.ASYNCHRONOUS_SERIAL)
  411. self._command_strobe(StrobeAddress.STX)
  412. try:
  413. # > In TX, the GDO0 pin is used for data input (TX data).
  414. yield Pin.GDO0
  415. finally:
  416. self._command_strobe(StrobeAddress.SIDLE)
  417. self._set_transceive_mode(_TransceiveMode.FIFO)