micropython_eeprom/eeprom/i2c/eep_i2c.py

228 wiersze
7.0 KiB
Python
Czysty Zwykły widok Historia

# eep_i2c.py MicroPython test program for Microchip I2C EEPROM devices.
2019-12-11 09:23:17 +00:00
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2024 Peter Hinch
2019-12-11 09:23:17 +00:00
import uos
import time
from machine import I2C, Pin, SoftI2C
from eeprom_i2c import EEPROM, T24C512
2019-12-11 09:23:17 +00:00
# Return an EEPROM array. Adapt for platforms other than Pyboard or chips
# smaller than 64KiB.
def get_eep():
# Special code for Pyboard D: enable 3.3V output
2022-09-22 09:04:29 +00:00
if uos.uname().machine.split(" ")[0][:4] == "PYBD":
2019-12-11 09:23:17 +00:00
Pin.board.EN_3V3.value(1)
time.sleep(0.1) # Allow decouplers to charge
if uos.uname().sysname == "esp8266": # ESP8266 test fixture
eep = EEPROM(SoftI2C(scl=Pin(13, Pin.OPEN_DRAIN), sda=Pin(12, Pin.OPEN_DRAIN)), T24C512)
elif uos.uname().sysname == "esp32": # ChronoDot on ESP32-S3
eep = EEPROM(SoftI2C(scl=Pin(9, Pin.OPEN_DRAIN), sda=Pin(8, Pin.OPEN_DRAIN)), 256, addr=0x50)
else: # Pyboard D test fixture
eep = EEPROM(I2C(2), T24C512)
2022-09-22 09:04:29 +00:00
print("Instantiated EEPROM")
2019-12-11 09:23:17 +00:00
return eep
2022-09-22 09:04:29 +00:00
# Yield pseudorandom bytes (random module not available on all ports)
def psrand8(x=0x3FBA2):
while True:
x ^= (x & 0x1FFFF) << 13
x ^= x >> 17
x ^= (x & 0x1FFFFFF) << 5
yield x & 0xFF
# Given a source of pseudorandom bytes yield pseudorandom 256 byte buffer.
def psrand256(rand, ba=bytearray(256)):
while True:
for z in range(256):
ba[z] = next(rand)
yield ba
2019-12-11 09:23:17 +00:00
# Dumb file copy utility to help with managing EEPROM contents at the REPL.
def cp(source, dest):
2022-09-22 09:04:29 +00:00
if dest.endswith("/"): # minimal way to allow
dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/
try:
with open(source, "rb") as infile: # Caller should handle any OSError
with open(dest, "wb") as outfile: # e.g file not found
while True:
buf = infile.read(100)
outfile.write(buf)
if len(buf) < 100:
break
except OSError as e:
if e.errno == 28:
print("Insufficient space for copy.")
else:
raise
2019-12-11 09:23:17 +00:00
2022-09-22 09:04:29 +00:00
2019-12-17 17:10:43 +00:00
# ***** TEST OF DRIVER *****
def _testblock(eep, bs):
if bs >= len(eep):
bs = len(eep) // 2
2022-09-22 09:04:29 +00:00
d0 = b"this >"
d1 = b"<is the boundary"
2019-12-17 17:10:43 +00:00
d2 = d0 + d1
2022-09-22 09:04:29 +00:00
garbage = b"xxxxxxxxxxxxxxxxxxx"
2019-12-17 17:10:43 +00:00
start = bs - len(d0)
end = start + len(garbage)
2022-09-22 09:04:29 +00:00
eep[start:end] = garbage
res = eep[start:end]
2019-12-17 17:10:43 +00:00
if res != garbage:
2022-09-22 09:04:29 +00:00
return "Block test fail 1:" + str(list(res))
2019-12-17 17:10:43 +00:00
end = start + len(d0)
2022-09-22 09:04:29 +00:00
eep[start:end] = d0
2019-12-17 17:10:43 +00:00
end = start + len(garbage)
2022-09-22 09:04:29 +00:00
res = eep[start:end]
if res != b"this >xxxxxxxxxxxxx":
return "Block test fail 2:" + str(list(res))
2019-12-17 17:10:43 +00:00
start = bs
end = bs + len(d1)
2022-09-22 09:04:29 +00:00
eep[start:end] = d1
2019-12-17 17:10:43 +00:00
start = bs - len(d0)
end = start + len(d2)
2022-09-22 09:04:29 +00:00
res = eep[start:end]
2019-12-17 17:10:43 +00:00
if res != d2:
2022-09-22 09:04:29 +00:00
return "Block test fail 3:" + str(list(res))
2019-12-17 17:10:43 +00:00
Add support of Microchip AT24C32 and support of single chip for `EEPROM` class [Description] Adds support of: 1. Microchip AT24C32 (4 KiB) 2. Support of single chip setup for `EEPROM` class when single chip has address is 0x57 so entire set of chips (1 chip in our case) addresses are not started from 0x50. Example: ```python from eeprom_i2c import EEPROM, T24C32 eeprom = PROM(machine.I2C(0), T24C32) ``` Improves tests implementation by adding dependency injection of `EEPROM` object used during testing with support of old use case when object had not been provided for testing. Example (for AT24C32 connected to I2C0 of my Raspberry Pi Pico W) when we creating instance of `EEPROM` and then passing it to `full_test` and also providing proper block size for this chip: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ``` [Motivation] Have DS3231 with soldered AT24C32 chip and want to use both RTC and EEPROM. In my case AT24C32 has 0x57 as it's address and `EEPROM` class refused to work with this setup. [Testing] Executed `full_test` from `eep_i2c` against AT24C32 (with address 0x57) connected to my Raspberry Pi Pico W. Test code: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ```
2022-09-24 16:42:58 +00:00
def test(eep=None):
eep = eep if eep else get_eep()
2019-12-11 09:23:17 +00:00
sa = 1000
address_range = 256
if sa + address_range > len(eep):
sa = (len(eep) - address_range) // 2
2019-12-11 09:23:17 +00:00
for v in range(256):
eep[sa + v] = v
for v in range(256):
if eep[sa + v] != v:
2024-01-07 18:04:06 +00:00
print("Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v))
2019-12-11 09:23:17 +00:00
break
else:
2022-09-22 09:04:29 +00:00
print("Test of byte addressing passed")
2019-12-11 09:23:17 +00:00
data = uos.urandom(30)
sa = 2000
if sa + len(data) > len(eep):
sa = (len(eep) - len(data)) // 2
2022-09-22 09:04:29 +00:00
eep[sa : sa + 30] = data
if eep[sa : sa + 30] == data:
print("Test of slice readback passed")
2019-12-11 09:23:17 +00:00
2019-12-17 17:10:43 +00:00
block = 256
res = _testblock(eep, block)
if res is None:
2022-09-22 09:04:29 +00:00
print("Test block boundary {} passed".format(block))
2019-12-17 17:10:43 +00:00
else:
2022-09-22 09:04:29 +00:00
print("Test block boundary {} fail".format(block))
2019-12-17 17:10:43 +00:00
print(res)
block = eep._c_bytes
if eep._a_bytes > block:
res = _testblock(eep, block)
if res is None:
2022-09-22 09:04:29 +00:00
print("Test chip boundary {} passed".format(block))
2019-12-17 17:10:43 +00:00
else:
2022-09-22 09:04:29 +00:00
print("Test chip boundary {} fail".format(block))
2019-12-17 17:10:43 +00:00
print(res)
else:
2022-09-22 09:04:29 +00:00
print("Test chip boundary skipped: only one chip!")
pe = eep.get_page_size() + 1 # One byte past page
eep[pe] = 0xFF
write_length = min(257, len(eep))
eep[:write_length] = b"\0" * write_length
print("Test page size: ", end="")
if eep[pe]:
print("FAIL")
else:
print("passed")
2022-09-22 09:04:29 +00:00
2019-12-17 17:10:43 +00:00
# ***** TEST OF FILESYSTEM MOUNT *****
Add support of Microchip AT24C32 and support of single chip for `EEPROM` class [Description] Adds support of: 1. Microchip AT24C32 (4 KiB) 2. Support of single chip setup for `EEPROM` class when single chip has address is 0x57 so entire set of chips (1 chip in our case) addresses are not started from 0x50. Example: ```python from eeprom_i2c import EEPROM, T24C32 eeprom = PROM(machine.I2C(0), T24C32) ``` Improves tests implementation by adding dependency injection of `EEPROM` object used during testing with support of old use case when object had not been provided for testing. Example (for AT24C32 connected to I2C0 of my Raspberry Pi Pico W) when we creating instance of `EEPROM` and then passing it to `full_test` and also providing proper block size for this chip: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ``` [Motivation] Have DS3231 with soldered AT24C32 chip and want to use both RTC and EEPROM. In my case AT24C32 has 0x57 as it's address and `EEPROM` class refused to work with this setup. [Testing] Executed `full_test` from `eep_i2c` against AT24C32 (with address 0x57) connected to my Raspberry Pi Pico W. Test code: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ```
2022-09-24 16:42:58 +00:00
def fstest(eep=None, format=False):
eep = eep if eep else get_eep()
try:
2022-09-22 09:04:29 +00:00
uos.umount("/eeprom")
except OSError:
pass
# ***** CODE FOR FATFS *****
2022-09-22 09:04:29 +00:00
# if format:
# os.VfsFat.mkfs(eep)
# ***** CODE FOR LITTLEFS *****
2019-12-11 09:23:17 +00:00
if format:
uos.VfsLfs2.mkfs(eep)
# General
2019-12-11 09:23:17 +00:00
try:
2022-09-22 09:04:29 +00:00
uos.mount(eep, "/eeprom")
except OSError:
raise OSError("Can't mount device: have you formatted it?")
2022-09-22 09:04:29 +00:00
print('Contents of "/": {}'.format(uos.listdir("/")))
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
2019-12-11 09:23:17 +00:00
Add support of Microchip AT24C32 and support of single chip for `EEPROM` class [Description] Adds support of: 1. Microchip AT24C32 (4 KiB) 2. Support of single chip setup for `EEPROM` class when single chip has address is 0x57 so entire set of chips (1 chip in our case) addresses are not started from 0x50. Example: ```python from eeprom_i2c import EEPROM, T24C32 eeprom = PROM(machine.I2C(0), T24C32) ``` Improves tests implementation by adding dependency injection of `EEPROM` object used during testing with support of old use case when object had not been provided for testing. Example (for AT24C32 connected to I2C0 of my Raspberry Pi Pico W) when we creating instance of `EEPROM` and then passing it to `full_test` and also providing proper block size for this chip: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ``` [Motivation] Have DS3231 with soldered AT24C32 chip and want to use both RTC and EEPROM. In my case AT24C32 has 0x57 as it's address and `EEPROM` class refused to work with this setup. [Testing] Executed `full_test` from `eep_i2c` against AT24C32 (with address 0x57) connected to my Raspberry Pi Pico W. Test code: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ```
2022-09-24 16:42:58 +00:00
def cptest(eep=None): # Assumes pre-existing filesystem of either type
eep = eep if eep else get_eep()
2022-09-22 09:04:29 +00:00
if "eeprom" in uos.listdir("/"):
print("Device already mounted.")
2019-12-17 17:10:43 +00:00
else:
try:
2022-09-22 09:04:29 +00:00
uos.mount(eep, "/eeprom")
2019-12-17 17:10:43 +00:00
except OSError:
2022-09-22 09:04:29 +00:00
print("Fail mounting device. Have you formatted it?")
2019-12-17 17:10:43 +00:00
return
2022-09-22 09:04:29 +00:00
print("Mounted device.")
try:
cp(__file__, "/eeprom/")
# We may have the source file or a precompiled binary (*.mpy)
cp(__file__.replace("eep", "eeprom"), "/eeprom/")
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
except NameError:
print("Test cannot be performed by this MicroPython port. Consider using upysh.")
2022-09-22 09:04:29 +00:00
2019-12-17 17:10:43 +00:00
# ***** TEST OF HARDWARE *****
# Write pseudorandom data to entire array, then read back. Fairly rigorous test.
def full_test(eep=None):
Add support of Microchip AT24C32 and support of single chip for `EEPROM` class [Description] Adds support of: 1. Microchip AT24C32 (4 KiB) 2. Support of single chip setup for `EEPROM` class when single chip has address is 0x57 so entire set of chips (1 chip in our case) addresses are not started from 0x50. Example: ```python from eeprom_i2c import EEPROM, T24C32 eeprom = PROM(machine.I2C(0), T24C32) ``` Improves tests implementation by adding dependency injection of `EEPROM` object used during testing with support of old use case when object had not been provided for testing. Example (for AT24C32 connected to I2C0 of my Raspberry Pi Pico W) when we creating instance of `EEPROM` and then passing it to `full_test` and also providing proper block size for this chip: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ``` [Motivation] Have DS3231 with soldered AT24C32 chip and want to use both RTC and EEPROM. In my case AT24C32 has 0x57 as it's address and `EEPROM` class refused to work with this setup. [Testing] Executed `full_test` from `eep_i2c` against AT24C32 (with address 0x57) connected to my Raspberry Pi Pico W. Test code: ```python import machine from eeprom_i2c import EEPROM, T24C32 from eep_i2c import full_test def get_eep(): return EEPROM(machine.I2C(0), T24C32) def main(): print("App started") print("Running tests") eep = get_eep() full_test(eep, block_size=32) print("App finished") if __name__ == "__main__": main() ```
2022-09-24 16:42:58 +00:00
eep = eep if eep else get_eep()
print("Testing with 256 byte blocks of random data...")
r = psrand8() # Instantiate random byte generator
ps = psrand256(r) # Random 256 byte blocks
for sa in range(0, len(eep), 256):
ea = sa + 256
eep[sa:ea] = next(ps)
print(f"Address {sa}..{ea} written\r", end="")
print()
r = psrand8() # Instantiate new random byte generator with same seed
ps = psrand256(r) # Random 256 byte blocks
for sa in range(0, len(eep), 256):
ea = sa + 256
if eep[sa:ea] == next(ps):
print(f"Address {sa}..{ea} readback passed\r", end="")
2019-12-11 09:23:17 +00:00
else:
print(f"Address {sa}..{ea} readback failed.")
print()
def help():
st = """Available commands:
help() Print this text.
test() Basic fuctional test.
full_test() Read-write test of EEPROM chip(s).
fstest() Check or create a filesystem.
cptest() Check a filesystem by copying source files to it.
Utilities:
get_eep() Initialise and return an EEPROM instance.
cp() Very crude file copy utility.
"""
print(st)
help()