Skip to content

Commit 6b43ea8

Browse files
add EEPROM package
1 parent e085e91 commit 6b43ea8

File tree

3 files changed

+271
-0
lines changed

3 files changed

+271
-0
lines changed

eeprom/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: UTF-8 -*-
3+
4+
from .version import __version__
5+
6+
from .eeprom import EEPROM

eeprom/eeprom.py

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: UTF-8 -*-
3+
4+
"""
5+
I2C EEPROM driver for AT24Cxx
6+
7+
EEPROM data sheet: https://ww1.microchip.com/downloads/en/DeviceDoc/doc0336.pdf
8+
9+
MIT License
10+
Copyright (c) 2018 Mike Causer
11+
Extended 2023 by brainelectronics
12+
13+
Permission is hereby granted, free of charge, to any person obtaining a copy
14+
of this software and associated documentation files (the "Software"), to deal
15+
in the Software without restriction, including without limitation the rights
16+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
copies of the Software, and to permit persons to whom the Software is
18+
furnished to do so, subject to the following conditions:
19+
20+
The above copyright notice and this permission notice shall be included in all
21+
copies or substantial portions of the Software.
22+
23+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
SOFTWARE.
30+
"""
31+
32+
# system packages
33+
from machine import I2C
34+
from time import sleep_ms
35+
36+
37+
class _Subscriptable():
38+
def __getitem__(self, item):
39+
return None
40+
41+
42+
_subscriptable = _Subscriptable()
43+
44+
List = _subscriptable
45+
Optional = _subscriptable
46+
Union = _subscriptable
47+
48+
49+
class EEPROM(object):
50+
"""Driver for AT24Cxx I2C EEPROM"""
51+
52+
def __init__(self,
53+
addr: int = 0x50,
54+
pages: int = 128,
55+
bpp: int = 32,
56+
i2c: Optional[I2C] = None,
57+
at24x: int = 0) -> None:
58+
"""
59+
Constructs a new instance
60+
61+
:param addr: The I2C bus address of the EEPROM
62+
:type addr: int
63+
:param pages: The number of pages of the EEPROM
64+
:type pages: int
65+
:param bpp: The bytes per page
66+
:type bpp: int
67+
:param i2c: I2C object
68+
:type i2c: I2C
69+
:param at24x: The specific AT24Cxx, either 32, 64, 128, 256, 512
70+
:type at24x: int
71+
"""
72+
self._addr = addr
73+
74+
standard_eeproms = {
75+
32: [128, 32], # 4KiB 32Kbits, 128 pages, 32 bytes/page
76+
64: [256, 32], # 8KiB 64Kbits, 256 pages, 32 bytes/page
77+
128: [256, 64], # 16KiB 128Kbits, 256 pages, 64 bytes/page
78+
256: [512, 64], # 32KiB 256Kbits, 512 pages, 64 bytes/page
79+
512: [512, 128], # 64KiB 512Kbits, 512 pages, 128 bytes/page
80+
}
81+
82+
if at24x in standard_eeproms:
83+
self._pages, self._bpp = standard_eeproms[at24x]
84+
else:
85+
self._pages = pages
86+
self._bpp = bpp
87+
88+
if i2c is None:
89+
# default assignment, check the docs
90+
self._i2c = I2C(0)
91+
else:
92+
self._i2c = i2c
93+
94+
@property
95+
def addr(self) -> int:
96+
"""
97+
Get the EEPROM I2C bus address
98+
99+
:returns: EEPROM I2C bus address
100+
:rtype: int
101+
"""
102+
return self._addr
103+
104+
@property
105+
def capacity(self) -> int:
106+
"""
107+
Get the storage capacity of the EEPROM
108+
109+
:returns: EEPROM capacity of the EEPROM in bytes
110+
:rtype: int
111+
"""
112+
return self._pages * self._bpp
113+
114+
@property
115+
def pages(self) -> int:
116+
"""
117+
Get the number of EEPROM pages
118+
119+
:returns: Number of pages of the EEPROM
120+
:rtype: int
121+
"""
122+
return self._pages
123+
124+
@property
125+
def bpp(self) -> int:
126+
"""
127+
Get the bytes per page of the EEPROM
128+
129+
:returns: Bytes per pages of the EEPROM
130+
:rtype: int
131+
"""
132+
return self._bpp
133+
134+
def length(self) -> int:
135+
"""
136+
Get the EEPROM length
137+
138+
:returns: Number of cells in the EEPROM
139+
:rtype: int
140+
"""
141+
return self.capacity
142+
143+
def read(self, addr: int, nbytes: int = 1) -> bytes:
144+
"""
145+
Read bytes from the EEPROM
146+
147+
:param addr: The start address
148+
:type addr: int
149+
:param nbytes: The number of bytes to read
150+
:type nbytes: int
151+
152+
:returns: Data of EEPROM
153+
:rtype: bytes
154+
"""
155+
if addr > self.pages or addr < 0:
156+
raise ValueError("Read address outside of device address range")
157+
return self._i2c.readfrom_mem(self._addr, addr, nbytes, addrsize=16)
158+
159+
def write(self, addr: int, buf: Union[bytes, List[int], str]) -> None:
160+
"""
161+
Write data to the EEPROM
162+
163+
:param addr: The start address
164+
:type addr: int
165+
:param buf: The buffer to write to the EEPROM
166+
:type buf: Union[bytes, List[int], str]
167+
"""
168+
offset = addr % self._bpp
169+
partial = 0
170+
171+
if addr > self.capacity or addr < 0:
172+
raise ValueError("Write address outside of device address range")
173+
174+
# if addr + (len(buf) // self.bpp) > self.pages:
175+
if addr + len(buf) > self.capacity:
176+
raise ValueError("Data does not fit into device address range")
177+
178+
# partial page write
179+
if offset > 0:
180+
partial = self._bpp - offset
181+
self._i2c.writeto_mem(
182+
self._addr, addr, buf[0:partial], addrsize=16
183+
)
184+
sleep_ms(5)
185+
addr += partial
186+
187+
# full page write
188+
for i in range(partial, len(buf), self._bpp):
189+
self._i2c.writeto_mem(
190+
self._addr,
191+
addr + i - partial,
192+
buf[i:i + self._bpp],
193+
addrsize=16
194+
)
195+
sleep_ms(5)
196+
197+
def update(self, addr: int, buf: Union[bytes, List[int], str]) -> None:
198+
"""
199+
Update data in EEPROM
200+
201+
:param addr: The start address
202+
:type addr: int
203+
:param buf: The buffer to write to the EEPROM
204+
:type buf: Union[bytes, List[int], str]
205+
"""
206+
for idx, ele in enumerate(buf):
207+
this_addr = addr + idx
208+
# this_val = bytes([ele])
209+
210+
# works for string, int and bytes
211+
if isinstance(ele, int):
212+
this_val = ele.to_bytes(1, 'big')
213+
else:
214+
this_val = str(ele).encode()
215+
216+
current_value = self.read(addr=this_addr) # returns bytes
217+
if current_value != this_val:
218+
self.write(addr=this_addr, buf=this_val)
219+
# print("{}: {} -> {}".
220+
# format(this_addr, current_value, this_val))
221+
# else:
222+
# print("No need to update value at {}".format(this_addr))
223+
224+
def wipe(self) -> None:
225+
"""Wipe the complete EEPROM"""
226+
page_buff = b'\xff' * self.bpp
227+
for i in range(self.pages):
228+
# print("erase: {}".format(i * self.bpp))
229+
self.write(i * self.bpp, page_buff)
230+
231+
def print_pages(self, addr: int, nbytes: int) -> None:
232+
"""
233+
Print pages content with boundaries.
234+
235+
:param addr: The start address
236+
:type addr: int
237+
:param nbytes: The number of bytes to read
238+
:type nbytes: int
239+
"""
240+
unknown_data_first_page = addr % self.bpp
241+
unknown_data_last_page = self.bpp - (addr + nbytes) % self.bpp
242+
if unknown_data_last_page % self.bpp == 0:
243+
unknown_data_last_page = 0
244+
245+
data = self.read(addr=addr, nbytes=nbytes)
246+
extended_data = (
247+
b'?' * unknown_data_first_page +
248+
data +
249+
b'?' * unknown_data_last_page
250+
)
251+
252+
sliced_data = [
253+
extended_data[i: i + self.bpp] for i in range(0,
254+
len(extended_data),
255+
self.bpp)
256+
]
257+
print('Page {:->4}: 0 {} {}'.
258+
format('x', '-' * (self.bpp - len(str(self.bpp))), self.bpp))
259+
for idx, a_slice in enumerate(sliced_data):
260+
print('Page {:->4}: {}'.format(idx, a_slice))

eeprom/version.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: UTF-8 -*-
3+
4+
__version_info__ = ("0", "0", "0")
5+
__version__ = '.'.join(__version_info__)

0 commit comments

Comments
 (0)