1
0

__init__.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import enum
  2. import typing
  3. import spidev
  4. class CC1101:
  5. # > All transfers on the SPI interface are done
  6. # > most significant bit first.
  7. # > All transactions on the SPI interface start with
  8. # > a header byte containing a R/W bit, a access bit (B),
  9. # > and a 6-bit address (A5 - A0).
  10. # > [...]
  11. # > Table 45: SPI Address Space
  12. _WRITE_SINGLE_BYTE = 0x00
  13. # > Registers with consecutive addresses can be
  14. # > accessed in an efficient way by setting the
  15. # > burst bit (B) in the header byte. The address
  16. # > bits (A5 - A0) set the start address in an
  17. # > internal address counter. This counter is
  18. # > incremented by one each new byte [...]
  19. _WRITE_BURST = 0x40
  20. _READ_SINGLE_BYTE = 0x80
  21. _READ_BURST = 0xC0
  22. class _SPIAddress(enum.IntEnum):
  23. # see "Table 45: SPI Address Space"
  24. # > The configuration registers on the CC1101 are
  25. # > located on SPI addresses from 0x00 to 0x2E.
  26. FREQ2 = 0x0D
  27. FREQ1 = 0x0E
  28. FREQ0 = 0x0F
  29. # > For register addresses in the range 0x30-0x3D,
  30. # > the burst bit is used to select between
  31. # > status registers when burst bit is one, and
  32. # > between command strobes when burst bit is
  33. # > zero. [...]
  34. # > Because of this, burst access is not available
  35. # > for status registers and they must be accessed
  36. # > one at a time. The status registers can only be
  37. # > read.
  38. SRES = 0x30
  39. PARTNUM = 0x30
  40. VERSION = 0x31
  41. MARCSTATE = 0x35
  42. class MainRadioControlStateMachineState(enum.IntEnum):
  43. """
  44. MARCSTATE - Main Radio Control State Machine State
  45. """
  46. # > Figure 13: Simplified State Diagram
  47. IDLE = 0x01
  48. # 29.3 Status Register Details
  49. _SUPPORTED_PARTNUM = 0
  50. _SUPPORTED_VERSION = 0x14
  51. _CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ = 26e6
  52. # see "21 Frequency Programming"
  53. # > f_carrier = f_XOSC / 2**16 * (FREQ + CHAN * ((256 + CHANSPC_M) * 2**CHANSPC_E-2))
  54. _FREQUENCY_CONTROL_WORD_HERTZ_FACTOR = _CRYSTAL_OSCILLATOR_FREQUENCY_HERTZ / 2 ** 16
  55. def __init__(self) -> None:
  56. self._spi = spidev.SpiDev()
  57. def _reset(self) -> None:
  58. # TODO check if 0 required
  59. assert self._spi.xfer([self._SPIAddress.SRES, 0]) == [0x0F, 0x0F]
  60. def _read_burst(self, start_register: _SPIAddress, length: int) -> typing.List[int]:
  61. response = self._spi.xfer([start_register | self._READ_BURST] + [0] * length)
  62. assert len(response) == length + 1, response
  63. assert response[0] == 0, response
  64. return response[1:]
  65. def _read_status_register(self, register: _SPIAddress) -> int:
  66. values = self._read_burst(start_register=register, length=1)
  67. assert len(values) == 1, values
  68. return values[0]
  69. def _write_burst(
  70. self, start_register: _SPIAddress, values: typing.List[int]
  71. ) -> None:
  72. response = self._spi.xfer([start_register | self._WRITE_BURST] + values)
  73. assert len(response) == len(values) + 1, response
  74. assert all(v == 0x0F for v in response), response # TODO why?
  75. def __enter__(self) -> "CC1101":
  76. # https://docs.python.org/3/reference/datamodel.html#object.__enter__
  77. self._spi.open(0, 0)
  78. self._spi.max_speed_hz = 55700 # empirical
  79. self._reset()
  80. partnum = self._read_status_register(self._SPIAddress.PARTNUM)
  81. if partnum != self._SUPPORTED_PARTNUM:
  82. raise ValueError(
  83. "unexpected chip part number {} (expected: {})".format(
  84. partnum, self._SUPPORTED_PARTNUM
  85. )
  86. )
  87. version = self._read_status_register(self._SPIAddress.VERSION)
  88. if version != self._SUPPORTED_VERSION:
  89. raise ValueError(
  90. "unexpected chip version number {} (expected: {})".format(
  91. version, self._SUPPORTED_VERSION
  92. )
  93. )
  94. marcstate = self.get_main_radio_control_state_machine_state()
  95. if marcstate != self.MainRadioControlStateMachineState.IDLE:
  96. raise ValueError("expected marcstate idle (actual: {})".format(marcstate))
  97. return self
  98. def __exit__(self, exc_type, exc_value, traceback) -> bool:
  99. # https://docs.python.org/3/reference/datamodel.html#object.__exit__
  100. self._spi.close()
  101. return False
  102. def get_main_radio_control_state_machine_state(
  103. self
  104. ) -> MainRadioControlStateMachineState:
  105. return self.MainRadioControlStateMachineState(
  106. self._read_status_register(self._SPIAddress.MARCSTATE)
  107. )
  108. def get_marc_state(self) -> MainRadioControlStateMachineState:
  109. """
  110. alias for get_main_radio_control_state_machine_state()
  111. """
  112. return self.get_main_radio_control_state_machine_state()
  113. @classmethod
  114. def _frequency_control_word_to_hertz(cls, control_word: typing.List[int]) -> float:
  115. return (
  116. int.from_bytes(control_word, byteorder="big", signed=False)
  117. * cls._FREQUENCY_CONTROL_WORD_HERTZ_FACTOR
  118. )
  119. @classmethod
  120. def _hertz_to_frequency_control_word(cls, hertz: float) -> typing.List[int]:
  121. return list(
  122. round(hertz / cls._FREQUENCY_CONTROL_WORD_HERTZ_FACTOR).to_bytes(
  123. length=3, byteorder="big", signed=False
  124. )
  125. )
  126. def _get_base_frequency_control_word(self) -> typing.List[int]:
  127. # > The base or start frequency is set by the 24 bitfrequency
  128. # > word located in the FREQ2, FREQ1, FREQ0 registers.
  129. return self._read_burst(start_register=self._SPIAddress.FREQ2, length=3)
  130. def _set_base_frequency_control_word(self, control_word: typing.List[int]) -> None:
  131. self._write_burst(start_register=self._SPIAddress.FREQ2, values=control_word)
  132. def get_base_frequency_hertz(self) -> float:
  133. return self._frequency_control_word_to_hertz(
  134. self._get_base_frequency_control_word()
  135. )
  136. def set_base_frequency_hertz(self, freq: float) -> None:
  137. self._set_base_frequency_control_word(
  138. self._hertz_to_frequency_control_word(freq)
  139. )
  140. def __str__(self) -> str:
  141. return "CC1101(marcstate={}, base_frequency={:.2f}MHz)".format(
  142. self.get_main_radio_control_state_machine_state().name.lower(),
  143. self.get_base_frequency_hertz() / 10 ** 6,
  144. )