-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathadafruit_fram.py
executable file
·435 lines (368 loc) · 15.5 KB
/
adafruit_fram.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# SPDX-FileCopyrightText: 2018 Michael Schroeder for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_fram`
====================================================
CircuitPython/Python library to support the I2C and SPI FRAM Breakouts.
* Author(s): Michael Schroeder
Implementation Notes
--------------------
**Hardware:**
* Adafruit `I2C Non-Volatile FRAM Breakout
<https://www.adafruit.com/product/1895>`_ (Product ID: 1895)
* Adafruit `SPI Non-Volatile FRAM Breakout
<https://www.adafruit.com/product/1897>`_ (Product ID: 1897)
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
# imports
from micropython import const
try:
from typing import Optional, Union, Sequence
from digitalio import DigitalInOut
from busio import I2C, SPI
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_FRAM.git"
_MAX_SIZE_I2C = const(0x8000)
_MAX_SIZE_SPI = const(0x2000)
_I2C_MANF_ID = const(0x0A)
_I2C_PROD_ID = const(0x510)
_SPI_MANF_ID = const(0x04)
_SPI_PROD_ID = const(0x302)
_SPI_OPCODE_WREN = const(0x6) # Set write enable latch
_SPI_OPCODE_WRDI = const(0x4) # Reset write enable latch
_SPI_OPCODE_RDSR = const(0x5) # Read status register
_SPI_OPCODE_WRSR = const(0x1) # Write status register
_SPI_OPCODE_READ = const(0x3) # Read memory code
_SPI_OPCODE_WRITE = const(0x2) # Write memory code
_SPI_OPCODE_RDID = const(0x9F) # Read device ID
class FRAM:
"""
Driver base for the FRAM Breakout.
:param int max_size: The maximum size of the EEPROM
:param bool write_protect: Turns on/off initial write protection
:param DigitalInOut wp_pin: (Optional) Physical pin connected to the ``WP`` breakout pin.
Must be a ``DigitalInOut`` object.
"""
def __init__(
self,
max_size: int,
write_protect: bool = False,
wp_pin: Optional[DigitalInOut] = None,
) -> None:
self._max_size = max_size
self._wp = write_protect
self._wraparound = False
if not wp_pin is None:
self._wp_pin = wp_pin
# Make sure write_prot is set to output
self._wp_pin.switch_to_output()
self._wp_pin.value = self._wp
else:
self._wp_pin = wp_pin
@property
def write_wraparound(self) -> bool:
"""Determines if sequential writes will wrapaound highest memory address
(``len(FRAM) - 1``) address. If ``False``, and a requested write will
extend beyond the maximum size, an exception is raised.
"""
return self._wraparound
@write_wraparound.setter
def write_wraparound(self, value: bool) -> None:
if not isinstance(value, bool):
raise ValueError("Write wraparound must be 'True' or 'False'.")
self._wraparound = value
@property
def write_protected(self) -> bool:
"""The status of write protection. Default value on initialization is
``False``.
When a ``WP`` pin is supplied during initialization, or using
``write_protect_pin``, the status is tied to that pin and enables
hardware-level protection.
When no ``WP`` pin is supplied, protection is only at the software
level in this library.
"""
return self._wp if self._wp_pin is None else self._wp_pin.value
def __len__(self) -> int:
"""The size of the current FRAM chip. This is one more than the highest
address location that can be read or written to.
.. code-block:: python
fram = adafruit_fram.FRAM_xxx() # xxx = 'I2C' or 'SPI'
# size returned by len()
len(fram)
# can be used with range
for i in range(0, len(fram))
"""
return self._max_size
def __getitem__(self, address: Union[int, slice]) -> bytearray:
"""Read the value at the given index, or values in a slice.
.. code-block:: python
# read single index
fram[0]
# read values 0 thru 9 with a slice
fram[0:9]
"""
if isinstance(address, int):
if not 0 <= address < self._max_size:
raise ValueError(
"Address '{0}' out of range. It must be 0 <= address < {1}.".format(
address, self._max_size
)
)
buffer = bytearray(1)
read_buffer = self._read_address(address, buffer)
elif isinstance(address, slice):
if address.step is not None:
raise ValueError("Slice stepping is not currently available.")
regs = list(
range(
address.start if address.start is not None else 0,
address.stop if address.stop is not None else self._max_size,
)
)
if regs[0] < 0 or (regs[0] + len(regs)) > self._max_size:
raise ValueError(
"Address slice out of range. It must be 0 <= [starting address"
":stopping address] < {0}.".format(self._max_size)
)
buffer = bytearray(len(regs))
read_buffer = self._read_address(regs[0], buffer)
return read_buffer
def __setitem__(self, address: Union[int, slice], value: Union[int, Sequence[int]]):
"""Write the value at the given starting index.
.. code-block:: python
# write single index
fram[0] = 1
# write values 0 thru 4 with a list
fram[0:4] = [0,1,2,3]
"""
if self.write_protected:
raise RuntimeError("FRAM currently write protected.")
if isinstance(address, int):
if not isinstance(value, int):
raise ValueError("Data stored in an address must be an integer 0-255")
if not 0 <= address < self._max_size:
raise ValueError(
"Address '{0}' out of range. It must be 0 <= address < {1}.".format(
address, self._max_size
)
)
self._write(address, value, self._wraparound)
elif isinstance(address, slice):
if not isinstance(value, (bytes, bytearray, list, tuple)):
raise ValueError(
"Data must be bytes, bytearray, list, "
"or tuple for multiple addresses"
)
if (address.start is None) or (address.stop is None):
raise ValueError("Boundless slices are not supported")
if (address.step is not None) and (address.step != 1):
raise ValueError("Slice stepping is not currently available.")
if (address.start < 0) or (address.stop > self._max_size):
raise ValueError(
"Slice '{0}:{1}' out of range. All addresses must be 0 <= address < {2}.".format( # pylint: disable=line-too-long
address.start, address.stop, self._max_size
)
)
if len(value) < (len(range(address.start, address.stop))):
raise ValueError(
"Cannot set values with a list smaller than the number of indexes"
)
self._write(address.start, value, self._wraparound)
def _read_address(self, address: int, read_buffer: bytearray) -> bytearray:
# Implemented by subclass
raise NotImplementedError
def _write(
self, start_address: int, data: Union[int, Sequence[int]], wraparound: bool
) -> None:
# Implemened by subclass
raise NotImplementedError
class FRAM_I2C(FRAM):
"""I2C class for FRAM.
:param ~busio.I2C i2c_bus: The I2C bus the FRAM is connected to.
:param int address: I2C address of FRAM. Default address is ``0x50``.
:param bool write_protect: Turns on/off initial write protection.
Default is ``False``.
:param wp_pin: (Optional) Physical pin connected to the ``WP`` breakout pin.
Must be a ``digitalio.DigitalInOut`` object.
"""
# pylint: disable=too-many-arguments
def __init__(
self,
i2c_bus: I2C,
address: int = 0x50,
write_protect: bool = False,
wp_pin: Optional[DigitalInOut] = None,
) -> None:
from adafruit_bus_device.i2c_device import ( # pylint: disable=import-outside-toplevel
I2CDevice as i2cdev,
)
dev_id_addr = 0xF8 >> 1
read_buf = bytearray(3)
with i2cdev(i2c_bus, dev_id_addr) as dev_id:
dev_id.write_then_readinto(bytearray([(address << 1)]), read_buf)
manf_id = (read_buf[0] << 4) + (read_buf[1] >> 4)
prod_id = ((read_buf[1] & 0x0F) << 8) + read_buf[2]
if (manf_id != _I2C_MANF_ID) and (prod_id != _I2C_PROD_ID):
raise OSError("FRAM I2C device not found.")
self._i2c = i2cdev(i2c_bus, address)
super().__init__(_MAX_SIZE_I2C, write_protect, wp_pin)
def _read_address(self, address: int, read_buffer: bytearray) -> bytearray:
write_buffer = bytearray(2)
write_buffer[0] = address >> 8
write_buffer[1] = address & 0xFF
with self._i2c as i2c:
i2c.write_then_readinto(write_buffer, read_buffer)
return read_buffer
def _write(
self,
start_address: int,
data: Union[int, Sequence[int]],
wraparound: bool = False,
) -> None:
# Decided against using the chip's "Page Write", since that would require
# doubling the memory usage by creating a buffer that includes the passed
# in data so that it can be sent all in one `i2c.write`. The single-write
# method is slower, and forces us to handle wraparound, but I feel this
# is a better tradeoff for limiting the memory required for large writes.
buffer = bytearray(3)
if not isinstance(data, int):
data_length = len(data)
else:
data_length = 1
data = [data]
if (start_address + data_length) > self._max_size:
if wraparound:
pass
else:
raise ValueError(
"Starting address + data length extends beyond"
" FRAM maximum address. Use ``write_wraparound`` to"
" override this warning."
)
with self._i2c as i2c:
for i in range(0, data_length):
if not (start_address + i) > self._max_size - 1:
buffer[0] = (start_address + i) >> 8
buffer[1] = (start_address + i) & 0xFF
else:
buffer[0] = ((start_address + i) - self._max_size + 1) >> 8
buffer[1] = ((start_address + i) - self._max_size + 1) & 0xFF
buffer[2] = data[i]
i2c.write(buffer)
# pylint: disable=no-member
@FRAM.write_protected.setter
def write_protected(self, value: bool) -> None:
if not isinstance(value, bool):
raise ValueError("Write protected value must be 'True' or 'False'.")
self._wp = value
if not self._wp_pin is None:
self._wp_pin.value = value
class FRAM_SPI(FRAM):
"""SPI class for FRAM.
:param ~busio.SPI spi_bus: The SPI bus the FRAM is connected to.
:param ~digitalio.DigitalInOut spi_cs: The SPI CS pin.
:param bool write_protect: Turns on/off initial write protection.
Default is ``False``.
:param wp_pin: (Optional) Physical pin connected to the ``WP`` breakout pin.
Must be a ``digitalio.DigitalInOut`` object.
:param int baudrate: SPI baudrate to use. Default is ``1000000``.
:param int max_size: Size of FRAM in Bytes. Default is ``8192``.
"""
# pylint: disable=too-many-arguments,too-many-locals
def __init__(
self,
spi_bus: SPI,
spi_cs: DigitalInOut,
write_protect: bool = False,
wp_pin: Optional[DigitalInOut] = None,
baudrate: int = 100000,
max_size: int = _MAX_SIZE_SPI,
):
from adafruit_bus_device.spi_device import ( # pylint: disable=import-outside-toplevel
SPIDevice as spidev,
)
_spi = spidev(spi_bus, spi_cs, baudrate=baudrate)
read_buffer = bytearray(4)
with _spi as spi:
spi.write(bytearray([_SPI_OPCODE_RDID]))
spi.readinto(read_buffer)
prod_id = (read_buffer[3] << 8) + (read_buffer[2])
if (read_buffer[0] != _SPI_MANF_ID) and (prod_id != _SPI_PROD_ID):
raise OSError("FRAM SPI device not found.")
self._spi = _spi
super().__init__(max_size, write_protect, wp_pin)
def _read_address(self, address: int, read_buffer: bytearray) -> bytearray:
write_buffer = bytearray(4)
write_buffer[0] = _SPI_OPCODE_READ
if self._max_size > 0xFFFF:
write_buffer[1] = (address >> 16) & 0xFF
write_buffer[2] = (address >> 8) & 0xFF
write_buffer[3] = address & 0xFF
else:
write_buffer[1] = (address >> 8) & 0xFF
write_buffer[2] = address & 0xFF
with self._spi as spi:
spi.write(write_buffer)
spi.readinto(read_buffer)
return read_buffer
def _write(
self,
start_address: int,
data: Union[int, Sequence[int]],
wraparound: bool = False,
) -> None:
buffer = bytearray(4)
if not isinstance(data, int):
data_length = len(data)
else:
data_length = 1
data = [data]
if (start_address + data_length) > self._max_size:
if wraparound:
pass
else:
raise ValueError(
"Starting address + data length extends beyond"
" FRAM maximum address. Use 'wraparound=True' to"
" override this warning."
)
with self._spi as spi:
spi.write(bytearray([_SPI_OPCODE_WREN]))
with self._spi as spi:
buffer[0] = _SPI_OPCODE_WRITE
if self._max_size > 0xFFFF:
buffer[1] = (start_address >> 16) & 0xFF
buffer[2] = (start_address >> 8) & 0xFF
buffer[3] = start_address & 0xFF
else:
buffer[1] = (start_address >> 8) & 0xFF
buffer[2] = start_address & 0xFF
spi.write(buffer)
for i in range(0, data_length):
spi.write(bytearray([data[i]]))
with self._spi as spi:
spi.write(bytearray([_SPI_OPCODE_WRDI]))
@FRAM.write_protected.setter
def write_protected(self, value: bool) -> None:
# While it is possible to protect block ranges on the SPI chip,
# it seems superfluous to do so. So, block protection always protects
# the entire memory (BP0 and BP1).
if not isinstance(value, bool):
raise ValueError("Write protected value must be 'True' or 'False'.")
self._wp = value
write_buffer = bytearray(2)
write_buffer[0] = _SPI_OPCODE_WRSR
if value:
write_buffer[1] = 0x8C # set WPEN, BP0, and BP1
else:
write_buffer[1] = 0x00 # clear WPEN, BP0, and BP1
with self._spi as spi:
spi.write(write_buffer)
if self._wp_pin is not None:
self._wp_pin.value = value