/ adafruit_boardtest / boardtest_spi.py
boardtest_spi.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2018 Shawn Hymel for Adafruit Industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_boardtest.boardtest_spi`
 24  ====================================================
 25  Performs random writes and reads to SPI EEPROM.
 26  
 27  Run this script as its own main.py to individually run the test, or compile
 28  with mpy-cross and call from separate test script.
 29  
 30  * Author(s): Shawn Hymel for Adafruit Industries
 31  
 32  Implementation Notes
 33  --------------------
 34  
 35  **Hardware:**
 36  
 37  * `Microchip 25AA040A SPI EEPROM <https://www.digikey.com/product-detail/en/\
 38  microchip-technology/25AA040A-I-P/25AA040A-I-P-ND/1212469>`_
 39  
 40  **Software and Dependencies:**
 41  
 42  * Adafruit CircuitPython firmware for the supported boards:
 43    https://github.com/adafruit/circuitpython/releases
 44  * Adafruit's Bus Device library:
 45    https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 46  
 47  """
 48  
 49  import random
 50  import time
 51  
 52  import board
 53  import digitalio
 54  import busio
 55  
 56  __version__ = "0.0.0-auto.0"
 57  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BoardTest.git"
 58  
 59  # Constants
 60  MOSI_PIN_NAME = "MOSI"
 61  MISO_PIN_NAME = "MISO"
 62  SCK_PIN_NAME = "SCK"
 63  CS_PIN_NAME = "D2"
 64  BAUD_RATE = 100000  # Bits per second
 65  NUM_SPI_TESTS = 10  # Number of times to write and read EEPROM values
 66  
 67  # Microchip 25AA040A EEPROM SPI commands and bits
 68  EEPROM_SPI_WRSR = 0x01
 69  EEPROM_SPI_WRITE = 0x02
 70  EEPROM_SPI_READ = 0x03
 71  EEPROM_SPI_WRDI = 0x04
 72  EEPROM_SPI_RDSR = 0x05
 73  EEPROM_SPI_WREN = 0x06
 74  EEPROM_SPI_WIP_BIT = 0
 75  EEPROM_SPI_MAX_ADDR = 255  # Self-imposed max memory address
 76  EEPROM_I2C_MAX_ADDR = 255  # Self-imposed max memory address
 77  
 78  # Test result strings
 79  PASS = "PASS"
 80  FAIL = "FAIL"
 81  NA = "N/A"
 82  
 83  # Wait for WIP bit to go low
 84  def _eeprom_spi_wait(spi, csel, timeout=1.0):
 85  
 86      # Continually read from STATUS register
 87      timestamp = time.monotonic()
 88      while time.monotonic() < timestamp + timeout:
 89  
 90          # Perfrom RDSR operation
 91          csel.value = False
 92          result = bytearray(1)
 93          spi.write(bytearray([EEPROM_SPI_RDSR]))
 94          spi.readinto(result)
 95          csel.value = True
 96  
 97          # Mask out and compare WIP bit
 98          if (result[0] & (1 << EEPROM_SPI_WIP_BIT)) == 0:
 99              return True
100  
101      return False
102  
103  
104  # Write to address. Returns status (True for successful write, False otherwise)
105  def _eeprom_spi_write_byte(spi, csel, address, data, timeout=1.0):
106  
107      # Make sure address is only one byte:
108      if address > 255:
109          return False
110  
111      # Make sure data is only one byte:
112      if data > 255:
113          return False
114  
115      # Wait for WIP to be low
116      if not _eeprom_spi_wait(spi, csel, timeout):
117          return False
118  
119      # Enable writing
120      csel.value = False
121      spi.write(bytearray([EEPROM_SPI_WREN]))
122      csel.value = True
123  
124      # Write to address
125      csel.value = False
126      spi.write(bytearray([EEPROM_SPI_WRITE, address, data]))
127      csel.value = True
128  
129      return True
130  
131  
132  # Read from address. Returns tuple [status, result]
133  def _eeprom_spi_read_byte(spi, csel, address, timeout=1.0):
134  
135      # Make sure address is only one byte:
136      if address > 255:
137          return False, bytearray()
138  
139      # Wait for WIP to be low
140      if not _eeprom_spi_wait(spi, csel, timeout):
141          return False, bytearray()
142  
143      # Read byte from address
144      csel.value = False
145      result = bytearray(1)
146      spi.write(bytearray([EEPROM_SPI_READ, address]))
147      spi.readinto(result)
148      csel.value = True
149  
150      return True, result
151  
152  
153  def run_test(
154      pins,
155      mosi_pin=MOSI_PIN_NAME,
156      miso_pin=MISO_PIN_NAME,
157      sck_pin=SCK_PIN_NAME,
158      cs_pin=CS_PIN_NAME,
159  ):
160  
161      """
162      Performs random writes and reads to file on attached SD card.
163  
164      :param list[str] pins: list of pins to run the test on
165      :param str mosi_pin: pin name of SPI MOSI
166      :param str miso_pin: pin name of SPI MISO
167      :param str sck_pin: pin name of SPI SCK
168      :param str cs_pin: pin name of SPI CS
169      :return: tuple(str, list[str]): test result followed by list of pins tested
170      """
171  
172      # Write values to SPI EEPROM and verify the values match
173      if list(set(pins).intersection(set([mosi_pin, miso_pin, sck_pin]))):
174  
175          # Tell user to connect EEPROM chip
176          print("Connect a Microchip 25AA040A EEPROM SPI chip.")
177          print("Connect " + cs_pin + " to the CS pin on the 25AA040.")
178          print("Press enter to continue.")
179          input()
180  
181          # Configure CS pin
182          csel = digitalio.DigitalInOut(getattr(board, cs_pin))
183          csel.direction = digitalio.Direction.OUTPUT
184          csel.value = True
185  
186          # Set up SPI
187          spi = busio.SPI(
188              getattr(board, sck_pin),
189              MOSI=getattr(board, mosi_pin),
190              MISO=getattr(board, miso_pin),
191          )
192  
193          # Wait for SPI lock
194          while not spi.try_lock():
195              pass
196          spi.configure(baudrate=BAUD_RATE, phase=0, polarity=0)
197  
198          # Pick a random address, write to it, read from it, and see if they match
199          pass_test = True
200          for _ in range(NUM_SPI_TESTS):
201  
202              # Randomly pick an address and a data value (one byte)
203              mem_addr = random.randint(0, EEPROM_SPI_MAX_ADDR)
204              mem_data = random.randint(0, 255)
205              print("Address:\t" + hex(mem_addr))
206              print("Writing:\t" + hex(mem_data))
207  
208              # Try writing this random value to the random address
209              result = _eeprom_spi_write_byte(spi, csel, mem_addr, mem_data)
210              if not result:
211                  print("FAIL: SPI could not communicate")
212                  pass_test = False
213                  break
214  
215              # Try reading the written value back from EEPRom
216              result = _eeprom_spi_read_byte(spi, csel, mem_addr)
217              print("Read:\t\t" + hex(result[1][0]))
218              print()
219              if not result[0]:
220                  print("FAIL: SPI could not communicate")
221                  pass_test = False
222                  break
223  
224              # Compare the read value to the original value
225              if result[1][0] != mem_data:
226                  print("FAIL: Data does not match")
227                  pass_test = False
228                  break
229  
230          # Release SPI pins
231          spi.deinit()
232  
233          # Return results
234          if pass_test:
235              return PASS, [mosi_pin, miso_pin, sck_pin]
236  
237          return FAIL, [mosi_pin, miso_pin, sck_pin]
238  
239      # Else (no pins found)
240      print("No SPI pins found")
241      return NA, []
242  
243  
244  def _main():
245  
246      # List out all the pins available to us
247      pins = list(dir(board))
248      print()
249      print("All pins found:", end=" ")
250  
251      # Print pins
252      for pin in pins:
253          print(pin, end=" ")
254      print("\n")
255  
256      # Run test
257      result = run_test(pins)
258      print()
259      print(result[0])
260      print("Pins tested: " + str(result[1]))
261  
262  
263  # Execute only if run as main.py or code.py
264  if __name__ == "__main__":
265      _main()