Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
TristanWarder committed Sep 28, 2023
1 parent fd48d2b commit 8ae9acc
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 0 deletions.
198 changes: 198 additions & 0 deletions AT24MAC_EEPROM.py
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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()

```
38 changes: 38 additions & 0 deletions examples/basic_read_write.py
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]"}
]
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"]

0 comments on commit 8ae9acc

Please sign in to comment.