diff --git a/AT24MAC_EEPROM.py b/AT24MAC_EEPROM.py new file mode 100644 index 0000000..bc74826 --- /dev/null +++ b/AT24MAC_EEPROM.py @@ -0,0 +1,198 @@ +# Written by FACTS Engineering +# Copyright (c) 2023 FACTS Engineering, LLC +# Licensed under the MIT license. +""" +`AT24MAC_EEPROM` +================================================================================ + +AT24MACx02 EEPROM Library + +Provides an interface to read and write from EEPROM memory +as well as retrieve the unique MAC Address and serial number + +Compatible with AT24MAC402 and AT24MAC602 devices using I2C + +* Author(s): Adam Cummick, Tristan Warder +""" + +import time +from adafruit_bus_device.i2c_device import I2CDevice +from micropython import const + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/facts-engineering/CircuitPython_AT24MAC_EEPROM" + +_EEPROM_LENGTH = const(256) # bytes +_PAGE_SIZE = const(16) +_WRITE_TIME = 0.005 + +_EUI_LOCATION = b"\x9A" +_MAC_ADDR_LENGTH = const(6) + +# Uncomment for AT24MAC602 with EUI64 8 byte MAC Address +# _EUI_LOCATION = b'\x98' +# _MAC_ADDR_LENGTH = const(8) + +_SERIAL_NUMBER_LOCATION = b"\x80" +_SERIAL_NUMBER_LENGTH = const(16) + + +class AT24MAC: + """Class to interface with EEPROM device""" + + def __init__(self, i2c, address_pins=0b100): + self.eeprom_device = I2CDevice(i2c, 0x50 | address_pins, True) + self.eui_device = I2CDevice(i2c, 0x58 | address_pins, True) + self.mac = self.read_mac_address() + self.serial_number = self.read_serial_number() + + def read_serial_number(self): + """Reads the unique serial number for the Device""" + serial_number = bytearray(_SERIAL_NUMBER_LENGTH) + + with self.eui_device as device: + device.write_then_readinto(_SERIAL_NUMBER_LOCATION, serial_number) + serial_number = int.from_bytes(serial_number, "big") + + return serial_number + + def read_mac_address(self): + """Reads the 6 byte mac address from the + AT24MAC402 device + """ + mac_address = bytearray(_MAC_ADDR_LENGTH) + + with self.eui_device as device: + device.write_then_readinto(_EUI_LOCATION, mac_address) + + return mac_address + + def __getitem__(self, address): + if isinstance(address, int): + return self._read(address) + if isinstance(address, slice): + if address.start is not None: + start = address.start + if (start < 0) or (start >= _EEPROM_LENGTH): + raise ValueError("Start Address goes beyond memory limit") + else: + start = 0 + if address.stop is not None: + stop = address.stop + if (stop < 0) or (stop >= _EEPROM_LENGTH): + raise ValueError("Stop Address goes beyond memory limit") + else: + stop = _EEPROM_LENGTH + length = stop - start + return self._read(start, length) + + raise ValueError("Item must be integer or slice") + + def __setitem__(self, address, data): + if isinstance(address, int): + self._write(address, data) + elif isinstance(address, slice): + if address.start is not None: + start = address.start + if (start < 0) or (start >= _EEPROM_LENGTH): + raise ValueError("Start Address goes beyond memory limit") + else: + start = 0 + if address.stop is not None: + stop = address.stop + if (stop < 0) or (stop >= _EEPROM_LENGTH): + raise ValueError("Stop Address goes beyond memory limit") + else: + stop = _EEPROM_LENGTH + + self._write(start, data) + else: + raise ValueError("Item must be integer or slice") + + def __len__(self): + return _EEPROM_LENGTH + + def _read(self, data_address, length=1): + """Reads data back from EEPROM at specified address + length defaults to 1, but can be specified + data is returned as bytearray + """ + if (length + data_address) > _EEPROM_LENGTH: + raise ValueError( + f"Data address {data_address} and length {length} go beyond memory limit" + ) + + data_bytes = bytearray(length) + data_address = data_address.to_bytes(1, "big") + + with self.eeprom_device as device: + device.write_then_readinto(data_address, data_bytes) + + if length > 1: + return data_bytes + + return data_bytes[0] + + def _write(self, data_address, data): + """writes a byte or bytearray to device at specified address + data length is determined by bytearray passed in. Data is compared to + the current page before writing to preserve EEPROM life. + When writing larger than 1 page, memory is only checked page by page + """ + if not isinstance(data, (bytearray, bytes)): + try: + if 0x00 <= data <= 0xFF: + data = data.to_bytes(1, "big") + except TypeError: + data = bytearray(data) + + if (len(data) + data_address) > _EEPROM_LENGTH: + raise ValueError( + f"Data address {data_address} and length {len(data)} go beyond memory limit" + ) + + if len(data) > 1: + total_pages = int(len(data) / _PAGE_SIZE) + remaining_bytes = len(data) % _PAGE_SIZE + offset = _PAGE_SIZE - (data_address % _PAGE_SIZE) + if offset == 16: + offset = 0 + remaining_bytes -= offset + else: + offset = 0 + total_pages = 0 + remaining_bytes = 1 + + current_address = data_address + if offset > 0: + self._write_page(data_address, data[0:offset]) + current_address += offset + if total_pages > 0: + for i in range(offset, total_pages * _PAGE_SIZE, _PAGE_SIZE): + this_page = data[i : i + _PAGE_SIZE] + self._write_page(current_address, this_page) + current_address += _PAGE_SIZE + if remaining_bytes > 0: + self._write_page(current_address, data[offset + total_pages * _PAGE_SIZE :]) + + def _write_page(self, data_address, data): + """Writes a single page to eeprom. Data is compared to current data + before writing to extend EEPROM life. Delay cycle is included for write + latency + """ + + if not self._does_data_match(data_address, data): + data_address = data_address.to_bytes(1, "big") + data_message = data_address + data + + with self.eeprom_device as device: + device.write(data_message) + time.sleep(_WRITE_TIME) + + def _does_data_match(self, data_address, data): + """Compares data passed in with the data at the specified address""" + + current_values = self._read(data_address, len(data)) + if len(data) == 1: + current_values = bytes([current_values]) + return current_values == data diff --git a/README.md b/README.md new file mode 100644 index 0000000..0715a39 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# circuitpython-at24mac-eeprom + +Driver to interface with AT24MAC402 and AT24MAC602 devices using I2C + +## Usage + +the AT24MACx02 devices are EEPROM devices with a built-in MAC address. + +```python +import at24mac +import board +i2c = busio.I2C(board.SCL, board.SDA) +eeprom = at24mac_eeprom.AT24MAC(i2c) + +print(eeprom.mac) # Format for use with Wiznet5k +print([hex(val) for val in eeprom.mac]) # Readable format +print(eeprom.serial_number) +print() + +# Write and read to address 0 +eeprom[0] = 76 +print(eeprom[0]) +print() + +# Write and read to address 100-104 +eeprom[100] = [6, 7, 8, 9, 10] +print([val for val in eeprom[100:105]]) +print() + +``` \ No newline at end of file diff --git a/examples/basic_read_write.py b/examples/basic_read_write.py new file mode 100644 index 0000000..cfecae7 --- /dev/null +++ b/examples/basic_read_write.py @@ -0,0 +1,38 @@ +# Basic Example to read and write from AT24MACx02 EEPROM Devices + +# Written by FACTS Engineering +# Copyright (c) 2023 FACTS Engineering, LLC +# Licensed under the MIT license. + +# + +import board +import busio +import at24mac_eeprom + +# Create EEPROM Object +i2c = busio.I2C(board.ATMAC_SCL, board.ATMAC_SDA) +# i2c = busio.I2C(board.SCL, board.SDA) # For external I2C devices +eeprom = at24mac_eeprom.AT24MAC(i2c) + +# Address lines default to 0b100. They can be specified if needed. +# eeprom = at24mac_eeprom.AT24MAC(i2c, 0b101) + +# Print out MAC address and serial number +print(eeprom.mac) # Format for use with Wiznet5k +print([hex(val) for val in eeprom.mac]) # Readable format +print(eeprom.serial_number) +print() + +# Write and read to address 0 using the device object like an array +eeprom[0] = 76 +print(eeprom[0]) +print() + +# Write and read to address 100 using array slices +eeprom[100] = [6, 7, 8, 9, 10] +print([val for val in eeprom[100:105]]) +print() + +while True: + pass diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..04c3d05 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = [ + "setuptools", + "wheel", + "setuptools-scm", +] + +[project] +name = "circuitpython-at24mac-eeprom" +description = "A CircuitPython library to interface with AT24MAC402 and AT24MAC602 devices." +version = "0.0.0+auto.0" +readme = "README.md" +authors = [ + {name = "Adam Cummick", email = "adamc@facts-eng.com"} +] +urls = {Homepage = "https://github.com/facts-engineering/CircuitPython_AT24MAC_EEPROM"} +keywords = [ + "adafruit", + "circuitpython", + "micropython", + "eeprom", + "at24mac", + "at24mac_eeprom" +] +license = {text = "MIT"} +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Embedded Systems", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] + +[tool.setuptools] +# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, +# CHANGE `py_modules = ['...']` TO `packages = ['...']` +py-modules = ["at24mac_eeprom"] \ No newline at end of file