Skip to content

Initial FRAM I2C #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 4, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 63 additions & 96 deletions adafruit_fram.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ class FRAM:
def __init__(self, max_size, write_protect=False, wp_pin=None):
self._max_size = max_size
self._wp = write_protect
self._wraparound = False
if not wp_pin is None:
import digitalio
self._wp_pin = digitalio.DigitalInOut(wp_pin)
#import digitalio
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
Expand All @@ -81,6 +82,20 @@ def max_size(self):
"""
return self._max_size

@property
def write_wraparound(self):
""" Determines if sequential writes will wrapaound the ``FRAM.max_size``
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):
if not value in (True, False):
raise ValueError("Write wraparound must be 'True' or 'False'.")
self._wraparound = value

@property
def write_protected(self):
""" The status of write protection. Default value on initialization is
Expand All @@ -93,103 +108,58 @@ def write_protected(self):
When no ``WP`` pin is supplied, protection is only at the software
level in this library.
"""
if not self._wp_pin is None:
status = self._wp_pin.value
else:
status = self._wp
return status
return self._wp if self._wp_pin is None else self._wp_pin.value

@write_protected.setter
def write_protected(self, value):
self._wp = value
if not self._wp_pin is None:
self._wp_pin.value = value

def write_protect_pin(self, wp_pin, write_protect=False):
""" Assigns the write protection (``WP``) pin.
def __getitem__(self, key):
if isinstance(key, int):
if key > self._max_size:
raise ValueError("Register '{0}' greater than maximum FRAM size."
" ({1})".format(key, self._max_size))
return self._read_byte(key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe have this be:

return self._read_byte(key)[0]

so you just get back the value instead of a single element bytearray with the value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant to comment on this last night. With the change to __getitem__, I wanted to make the return value standard. I'm not completely opposed to sending back the value. Just felt that reduced API confusion was more beneficial.

elif isinstance(key, slice):
registers = list(range(key.start if not key.start is None else 0,
key.stop if not key.stop is None else self._max_size,
key.step if not key.step is None else 1))
if (registers[0] + len(registers)) > self._max_size:
raise ValueError("Register + Length greater than maximum FRAM size."
" ({0})".format(self._max_size))

read_buffer = bytearray(len(registers))
for i, register in enumerate(registers):
read_buffer[i] = self._read_byte(register)[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to read more than one byte at a time? That will be much faster.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, I don't remember why I chose to read one byte at a time. May have been an attempt to minimize memory use.

Just ran some comparisons; memory allocation is tripped at the same length of reads both ways. I'll switch it. (it is much faster)

return read_buffer

def __setitem__(self, key, value):
if self.write_protected:
raise RuntimeError("FRAM currently write protected.")

:param: wp_pin: The ``board.PIN`` object connected to the ``WP`` pin
on the breakout board/chip. To remove a previously
set ``WP`` pin, set this value to ``None``.
:param: bool write_protect: Turn on/off write protection immediately
when setting the pin. Default is ``False``
if isinstance(key, int):
if not isinstance(value, int):
raise ValueError("Data must be an integer.")
if key.start > self._max_size:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like slice code? Trying to use .start on an int type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm...swear I fixed that on the fix the fixes commit.

raise ValueError("Requested register '{0}' greater than maximum"
" FRAM size. ({1})".format(key.start,
self._max_size))

"""
if not wp_pin is None:
import digitalio
self._wp_pin = digitalio.DigitalInOut(wp_pin)
# Make sure wp_pin is set to switch_to_output
self._wp_pin.switch_to_output()
self._wp_pin.value = write_protect
else:
if not self._wp_pin is None:
# Deinit the pin to release it
self._wp_pin.deinit()
self._wp_pin = None

def read(self, register, length=1):
""" Reads the data stored on the FRAM.

:param: int register: Register location to start reading. Range is:
``0`` to ``max_size``.
:param: int length: Length of registers to read from starting register.
This function will create a buffer the size of
``length``; larger buffers can cause memory
allocation problems on some platforms.
Range is ``1`` (default) to ``max_size``.
However, ``register`` + ``length`` cannot be
greater than ``max_size``.
"""
if length < 1:
raise ValueError("Length must be '1' or greater.")
if length > self._max_size:
raise ValueError("Length '{0}' greater than maximum FRAM size."
" ({1})".format(length, self._max_size))
if (register + length) > self._max_size:
raise ValueError("Register + Length greater than maximum FRAM size."
" ({0})".format(self._max_size))
read_buffer = bytearray(length)
for i in range(length):
read_buffer[i] = self._read_byte(register + i)[0]
return read_buffer
self._write_register(key.start, value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - trying to use .start on an int type.


def write_single(self, register, data):
""" Writes a single byte to the FRAM.
elif isinstance(key, slice):
if not isinstance(value, (bytearray, list, tuple)):
raise ValueError("Data must be either a bytearray, list, or tuple.")
if (key.start > self._max_size):
raise ValueError("Requested register '{0}' greater than maximum"
" FRAM size. ({1})".format(key.start,
self._max_size))
if not key.step is None:
raise ValueError("Slice steps are not allowed during write operations.")

:param: int register: Register location to write the byte data.
:param: int data: The data to write.
"""
if not isinstance(data, int):
raise ValueError("Data must be an integer.")
if self.write_protected:
raise RuntimeError("FRAM currently write protected.")
if register > self._max_size:
raise ValueError("Requested register '{0}' greater than maximum"
" FRAM size. ({1})".format(register,
self._max_size))
self._write_register(register, data)

def write_sequence(self, start_register, data, wraparound=False):
""" Writes sequential data to the FRAM.

:param: int start_register: Register location to start writing the
data.
:param: data: The data to write. Must be an iterable type of either
``bytearray``, ``list``, or ``tuple``.
:param: bool wraparound: Controls if sequential writes can wraparound
beyond the ``max_size`` to zero, when
``start_register`` + ``data length`` is
greater than ``max_size``.
"""
if not isinstance(data, (bytearray, list, tuple)):
raise ValueError("Data must be either a bytearray, list, or tuple.")
if self.write_protected:
raise RuntimeError("FRAM currently write protected.")
if start_register > self._max_size:
raise ValueError("Requested register '{0}' greater than maximum"
" FRAM size. ({1})".format(start_register,
self._max_size))
self._write_page(start_register, data, wraparound)
self._write_page(key.start, value, self._wraparound)

def _read_byte(self, register):
return self._read_register(register)
Expand All @@ -209,19 +179,16 @@ def _write_page(self, start_register, data, wraparound):
class FRAM_I2C(FRAM):
""" I2C class for FRAM.

:param: i2c_SCL: The I2C SCL pin. Must be a ``board.PIN`` object.
:param: i2c_SDA: The I2C SDA print. Must be a ``board.PIN`` object.
: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: Physical ``WP`` breakout pin. Must be a ``board.PIN``
object.
:param: wp_pin: Physical pin connected to the ``WP`` breakout pin.
Must be a ``digitalio.DigitalInOut`` object.
"""
#pylint: disable=too-many-arguments
def __init__(self, i2c_SCL, i2c_SDA, address=0x50, write_protect=False,
def __init__(self, i2c_bus, address=0x50, write_protect=False,
wp_pin=None):
from busio import I2C as i2c
i2c_bus = i2c(i2c_SCL, i2c_SDA)
i2c_bus.try_lock()
i2c_bus.writeto((0xF8 >> 1), bytearray([(address << 1)]), stop=False)
read_buf = bytearray(3)
Expand Down Expand Up @@ -266,7 +233,7 @@ def _write_page(self, start_register, data, wraparound=False):
pass
else:
raise ValueError("Starting register + data length extends beyond"
" FRAM maximum size. Use 'wraparound=True' to"
" FRAM maximum size. Use ``write_wraparound`` to"
" override this warning.")
with self._i2c as i2c:
for i in range(0, data_length):
Expand Down
26 changes: 14 additions & 12 deletions examples/fram_i2c_simpletest.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
## Simple Example For CircuitPython/Python I2C FRAM Library

import board
import busio
import adafruit_fram

## Create a FRAM object (default address used).

fram = adafruit_fram.FRAM_I2C(board.SCL, board.SDA)
i2c = busio.I2C(board.SCL, board.SDA)
fram = adafruit_fram.FRAM_I2C(i2c)

## Optional FRAM object with a different I2C address, as well
## as a pin to control the hardware write protection ('WP'
## pin on breakout). 'write_protected()' can be used
## independent of the hardware pin.

#fram = adafruit_fram.FRAM_I2C(board.SCL,
# board.SDA,
#import digitalio
#wp = digitalio.DigitalInOut(board.D10)
#fram = adafruit_fram.FRAM_I2C(i2c,
# address=0x53,
# wp_pin=board.D4)
# wp_pin=wp)

## Write a single-byte value to register address '0'

fram.write_single(0, 1)
fram[0] = 1

## Read that byte to ensure a proper write.
## Note: 'read()' returns a bytearray
## Note: reads return a bytearray

print(fram.read(0)[1])
print(fram[0])

## Or write a sequential value, then read the values back.
## Note: 'read()' returns a bytearray. It also allocates
## a buffer the size of 'length', which may cause
## Note: reads return a bytearray. Reads also allocate
## a buffer the size of slice, which may cause
## problems on memory-constrained platforms.

#values = list(range(100)) # or bytearray or tuple
#fram.write_sequence(0, values)
#fram.read(0, length=100)
#fram[0:100] = values
#print(fram[0:100])