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

Initial FRAM I2C #1

merged 19 commits into from
Nov 4, 2018

Conversation

sommersoft
Copy link
Collaborator

Here it be. Fixes Part 1 of CircuitPython Issue #665

I assume RTD sub-project will need to be created, but that can wait until I finish the SPI version (separate PR).

Also, I'm going to put in an issue to update the FRAM Learn Guide with CircuitPython. I'm happy to do it, just need to be added to the guide.

@sommersoft
Copy link
Collaborator Author

sommersoft commented Oct 2, 2018

Annnddd, Travis needs to be setup on this repo.

Also...needs librarians added to admins. (will this guy ever stop talking? 😆)

@kattni
Copy link
Contributor

kattni commented Oct 3, 2018

Added Librarians to the repo. Activated the repo on Travis, however it's not seeing the PR. I'll look more into it tomorrow.

@sommersoft sommersoft requested a review from a team October 3, 2018 02:29
Copy link
Contributor

@caternuson caternuson left a comment

Choose a reason for hiding this comment

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

General approach looks good. See comments about changes to interface.

adafruit_fram.py Outdated
status = self._wp_pin.value
else:
status = self._wp
return status
Copy link
Contributor

Choose a reason for hiding this comment

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

Could replace this entire function with:

return self._wp if self._wp_pin is None else self._wp_pin.value

adafruit_fram.py Outdated
if not self._wp_pin is None:
self._wp_pin.value = value

def write_protect_pin(self, wp_pin, write_protect=False):
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there scenarios where this would need to be set or changed after creating the object? If not, remove this and require passing it in through __init__.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I originally wrote it where the pin was only passed through during __init__. I added this later, as a "nicety" for the user. No specific scenario in mind... I am fine with removing it.

adafruit_fram.py Outdated
"""
if not wp_pin is None:
import digitalio
self._wp_pin = digitalio.DigitalInOut(wp_pin)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here about pin object vs. designation.

adafruit_fram.py Outdated
read_buffer[i] = self._read_byte(register + i)[0]
return read_buffer

def write_single(self, register, data):
Copy link
Contributor

Choose a reason for hiding this comment

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

Use __setitem__ for this to allow for fram[address] = value.

fram[0x23] = 0x42

adafruit_fram.py Outdated
self._max_size))
self._write_register(register, data)

def write_sequence(self, start_register, data, wraparound=False):
Copy link
Contributor

Choose a reason for hiding this comment

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

This can also be done through __setitem__.

fram[start_address] = data

adafruit_fram.py Outdated
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.")
i2c_bus.unlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

I2C bus creation should be done by the calling program and passed in. See how other I2C device drivers are doing this.
The MID/PID check to see if the device is connected and found is good though. Just change to doing that through the I2CDevice.

Copy link
Collaborator Author

@sommersoft sommersoft Oct 14, 2018

Choose a reason for hiding this comment

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

I can change to, and agree with, having the I2C bus passed in. However, I'll still need to use the raw bus to get the MID/PID due to the following:

  • The Device ID (MID/PID) is at specific address, which is different from the I2C address space that is passed in.
  • Reading the Device ID requires a certain order of writes/reads, which I2CDevice would not allow. When I2CDevice is initialized, it writes an empty buffer to the stored address, which would (did) interfere with the specified steps.

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

print(fram.read(0)[1])
Copy link
Contributor

Choose a reason for hiding this comment

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

Off by one. fram.read(0)[0]
But this will become

print(fram[0])

@caternuson
Copy link
Contributor

Just documenting my testing of the basic functionality of current version.

Adafruit CircuitPython 3.0.3 on 2018-10-10; Adafruit ItsyBitsy M4 Express with samd51g19
>>> import board
>>> import adafruit_fram
>>> fram = adafruit_fram.FRAM_I2C(board.SCL, board.SDA)
>>> fram.max_size
32767
>>> fram.write_single(0, 1)
>>> print(fram.read(0)[0])
1
>>> fram.write_protected
False
>>> fram.write_protected = True
>>> fram.write_protected
True
>>> fram.write_single(1, 23)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "adafruit_fram.py", line 165, in write_single
RuntimeError: FRAM currently write protected.
>>> 

adafruit_fram.py Outdated
self._wp_pin.deinit()
self._wp_pin = None

def read(self, register, length=1):
Copy link
Contributor

Choose a reason for hiding this comment

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

Use__getitem__ for this.

value = fram[0]
values = fram[23:42]

@sommersoft
Copy link
Collaborator Author

Example of slice handling. Its rudimentary...doesn't support negative values, but aligns with CPython range assignment other than that.

>>> fram[::1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./circuitpython_libs/Adafruit_CircuitPython_FRAM/adafruit_fram.py", line 128, in __getitem__
MemoryError: memory allocation failed, allocating 4096 bytes
>>> fram[:100]
bytearray(b'\x00\x01\x1elohello\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
>>> fram[1:10:10]
bytearray(b'\x01')
>>> fram[1]
bytearray(b'\x01')
>>> fram[1:10]
bytearray(b'\x01\x1elohello')
>>> fram[:32760]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./circuitpython_libs/Adafruit_CircuitPython_FRAM/adafruit_fram.py", line 128, in __getitem__
MemoryError: memory allocation failed, allocating 4096 bytes
>>> fram[32760:]
bytearray(b'\x00\x00\x00\x00\x00\x00\x00')
>>> fram[0:1:1] = [0,1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./circuitpython_libs/Adafruit_CircuitPython_FRAM/adafruit_fram.py", line 160, in __setitem__
ValueError: Slice steps are not allowed during write operations.
>>> fram[0:5] = [0,1,2,3,4]
>>> fram[0:5]
bytearray(b'\x00\x01\x02\x03\x04')

Copy link
Contributor

@caternuson caternuson left a comment

Choose a reason for hiding this comment

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

New item access looks good. Found a few more tweaks.

adafruit_fram.py Outdated
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.

adafruit_fram.py Outdated
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.

adafruit_fram.py Outdated
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.

#pylint: disable=too-many-arguments
def __init__(self, i2c_bus, address=0x50, write_protect=False,
wp_pin=None):
from adafruit_bus_device.i2c_device import I2CDevice as i2cdev
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have this here so it only gets imported when the I2C class is used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. We may end up refactoring the various classes into other files. It's been done in other libraries, but I don't think we're being too consistent about it. This will become more important as this grows with the addition of the code for the SPI class.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

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

A couple comments but looks good otherwise!

adafruit_fram.py Outdated

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)

adafruit_fram.py Outdated
self._wp_pin = wp_pin

@property
def max_size(self):
Copy link
Member

Choose a reason for hiding this comment

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

Should this be the standard __len__ instead? That way you can use range on it in a loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have no issue with changing to __len__, if that is more Pythonic. Since __getitem__ and __setitem__ are already documented as special members, I can just add this too.

@sommersoft
Copy link
Collaborator Author

Don't merge yet...

I need to fix __setitem__; slicing touchup and handling len(value) > slice[start:stop]

>>> fram[0:4] = bytearray(b'length>greater_than_slice')
>>> fram[0:4]
bytearray(b'leng')
>>> fram[0:15]
bytearray(b'length>greater_')

@tannewt
Copy link
Member

tannewt commented Oct 22, 2018

@sommersoft Ping me when you need another review.

@sommersoft
Copy link
Collaborator Author

@tannewt ready for another look.

note: dropped slicing altogether for __setitem__, and removed slice stepping for __getitem__.

@kattni
Copy link
Contributor

kattni commented Oct 29, 2018

@tannewt ping.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

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

Looks great! Thanks!

@tannewt tannewt merged commit 8c66050 into adafruit:master Nov 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants