/ adafruit_boardtest / boardtest_i2c.py
boardtest_i2c.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_i2c`
 24  ====================================================
 25  Performs random writes and reads to I2C 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 AT24HC04B I2C EEPROM <https://www.digikey.com/product-detail/en/\
 38  microchip-technology/AT24HC04B-PU/AT24HC04B-PU-ND/1886137>`_
 39  
 40  **Software and Dependencies:**
 41  
 42  * Adafruit CircuitPython firmware for the supported boards:
 43    https://github.com/adafruit/circuitpython/releases
 44  
 45  """
 46  
 47  import random
 48  import time
 49  
 50  import board
 51  import busio
 52  
 53  __version__ = "0.0.0-auto.0"
 54  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BoardTest.git"
 55  
 56  # Constants
 57  SDA_PIN_NAME = "SDA"
 58  SCL_PIN_NAME = "SCL"
 59  NUM_I2C_TESTS = 10  # Number of times to write and read EEPROM values
 60  EEPROM_I2C_MAX_ADDR = 255  # Self-imposed max memory address
 61  
 62  # Microchip AT24HC04B EEPROM I2C address
 63  EEPROM_I2C_ADDR = 0x50
 64  
 65  # Test result strings
 66  PASS = "PASS"
 67  FAIL = "FAIL"
 68  NA = "N/A"
 69  
 70  # Open comms to I2C EEPROM by trying a write to memory address
 71  def _eeprom_i2c_wait(i2c, i2c_addr, mem_addr, timeout=1.0):
 72  
 73      # Try to access the I2C EEPROM (it becomes unresonsive during a write)
 74      timestamp = time.monotonic()
 75      while time.monotonic() < timestamp + timeout:
 76          try:
 77              i2c.writeto(i2c_addr, bytearray([mem_addr]), end=1)
 78              return True
 79          except OSError:
 80              pass
 81  
 82      return False
 83  
 84  
 85  # Write to address. Returns status (True for successful write, False otherwise)
 86  def _eeprom_i2c_write_byte(i2c, i2c_addr, mem_addr, mem_data):
 87  
 88      # Make sure address is only one byte:
 89      if mem_addr > 255:
 90          return False
 91  
 92      # Make sure data is only one byte:
 93      if mem_data > 255:
 94          return False
 95  
 96      # Write data to memory at given address
 97      try:
 98          i2c.writeto(i2c_addr, bytearray([mem_addr, mem_data]))
 99      except OSError:
100          return False
101  
102      return True
103  
104  
105  # Read from address. Returns tuple [status, result]
106  def _eeprom_i2c_read_byte(i2c, i2c_addr, mem_addr, timeout=1.0):
107  
108      # Make sure address is only one byte:
109      if mem_addr > 255:
110          return False, bytearray()
111  
112      # Try writing to address (EEPROM is unresponsive while writing)
113      if not _eeprom_i2c_wait(i2c, i2c_addr, mem_addr, timeout):
114          return False, bytearray()
115  
116      # Finish the read
117      buf = bytearray(1)
118      i2c.writeto_then_readfrom(i2c_addr, bytearray([mem_addr]), buf)
119  
120      return True, buf
121  
122  
123  def run_test(pins, sda_pin=SDA_PIN_NAME, scl_pin=SCL_PIN_NAME):
124  
125      """
126      Performs random writes and reads to I2C EEPROM.
127  
128      :param list[str] pins: list of pins to run the test on
129      :param str sda_pin: pin name of I2C SDA
130      :param str scl_pin: pin name of I2C SCL
131      :return: tuple(str, list[str]): test result followed by list of pins tested
132      """
133  
134      # Write values to I2C EEPROM and verify the values match
135      if list(set(pins).intersection(set([sda_pin, scl_pin]))):
136  
137          # Tell user to connect EEPROM chip
138          print(
139              "Connect a Microchip AT24HC04B EEPROM I2C chip. "
140              + "Press enter to continue."
141          )
142          input()
143  
144          # Set up I2C
145          i2c = busio.I2C(getattr(board, scl_pin), getattr(board, sda_pin))
146  
147          # Wait for I2C lock
148          while not i2c.try_lock():
149              pass
150  
151          # Pick a random address, write to it, read from it, and see if they match
152          pass_test = True
153          for _ in range(NUM_I2C_TESTS):
154  
155              # Randomly pick an address and a data value (one byte)
156              mem_addr = random.randint(0, EEPROM_I2C_MAX_ADDR)
157              mem_data = random.randint(0, 255)
158              print("Address:\t" + hex(mem_addr))
159              print("Writing:\t" + hex(mem_data))
160  
161              # Try writing this random value to the random address
162              result = _eeprom_i2c_write_byte(i2c, EEPROM_I2C_ADDR, mem_addr, mem_data)
163              if not result:
164                  print("FAIL: I2C could not communicate")
165                  pass_test = False
166                  break
167  
168              # Try reading the written value back from EEPROM
169              result = _eeprom_i2c_read_byte(i2c, EEPROM_I2C_ADDR, mem_addr)
170              print("Read:\t\t" + hex(result[1][0]))
171              print()
172              if not result[0]:
173                  print("FAIL: I2C could not communicate")
174                  pass_test = False
175                  break
176  
177              # Compare the read value to the original value
178              if result[1][0] != mem_data:
179                  print("FAIL: Data does not match")
180                  pass_test = False
181                  break
182  
183          # Release I2C pins
184          i2c.deinit()
185  
186          # Store results
187          if pass_test:
188              return PASS, [sda_pin, scl_pin]
189  
190          return FAIL, [sda_pin, scl_pin]
191  
192      # Else (no pins found)
193      print("No I2C pins found")
194      return NA, []
195  
196  
197  def _main():
198  
199      # List out all the pins available to us
200      pins = list(dir(board))
201      print()
202      print("All pins found:", end=" ")
203  
204      # Print pins
205      for pin in pins:
206          print(pin, end=" ")
207      print("\n")
208  
209      # Run test
210      result = run_test(pins)
211      print()
212      print(result[0])
213      print("Pins tested: " + str(result[1]))
214  
215  
216  # Execute only if run as main.py or code.py
217  if __name__ == "__main__":
218      _main()