adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.05k stars 1.2k forks source link

i2c SLAVE #437

Closed mik00 closed 6 years ago

mik00 commented 6 years ago

One of the useful features of the Feather (and it's ilk) is that it can be used as one of many I2C slaves. It would be cool if the i2c drivers covered that case.

Thankyou for all the excellent, and hard work you have put into supporting CP.

tannewt commented 6 years ago

I'm going to mark this long term. We'll have to figure out a sane API for it. We're trying to avoid requiring knowledge of concurrency which could make acting as an I2C slave a bit challenging.

Anton-2 commented 6 years ago

A few days ago, I was sure that I needed I2C Slave on a trinket M0. Then I discovered the current state of ASF4, and the on going migration of CP to it, and decided that I could do without...

I was thinking of a very simple API : expose a bytearray as a small memory device thu I2C. The master could read/write bytes on it, and CP would provide (soft) callbacks to know when something was read or written.

This way, there is no python code when the timing is critical. Simple cases could be used without callback by simply polling some location and observing changes done by the master or the slave.

But, clearly, for more complicated tasks concurency will be a subject (eg accessing a multi-byte value while the master is writing it...)

@mik00 Does such a API cover your needs ? I may try to implement it if there is interest, and after trying simpler things with ASF4 first...

tannewt commented 6 years ago

@Anton-2 I like that API!

mik00 commented 6 years ago

Hi Antonin,

I'm sure that would have been just fine for my needs.

I was trying to overcome three problems:

  1. limited pins on the main controller board;
  2. finding off-the-shelf device-specific i2c driver boards that can be configured to a very large bus address range;
  3. lower cost.

For an app that needs to drive dozens, if not hundreds of simple asynchronous devices, your solution sounds ideal. There are plenty of work-arounds for specific cases, but your solution could be run on a single chip, with just a few lines of code. Ideal for some humongous artwork installations for example. I2c busses could even be formed into trees for the really large installations.

I craved the simplicity of your solution, but I pushed ahead with a C implementation in the interests of time.

Thanks for your answer Mik

https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon Virus-free. www.avast.com https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>

On Tue, Nov 14, 2017 at 9:54 AM, Antonin ENFRUN notifications@github.com wrote:

A few days ago, I was sure that I needed I2C Slave on a trinket M0. Then I discovered the current state of ASF4, and the on going migration of CP to it, and decided that I could do without...

I was thinking of a very simple API : expose a bytearray as a small memory device thu I2C. The master could read/write bytes on it, and CP would provide (soft) callbacks to know when something was read or written.

This way, there is no python code when the timing is critical. Simple cases could be used without callback by simply polling some location and observing changes done by the master or the slave.

But, clearly, for more complicated tasks concurency will be a subject (eg accessing a multi-byte value while the master is writing it...)

@mik00 https://github.com/mik00 Does such a API cover your needs ? I may try to implement it if there is interest, and after trying simpler things with ASF4 first...

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/adafruit/circuitpython/issues/437#issuecomment-344342141, or mute the thread https://github.com/notifications/unsubscribe-auth/AHHDZ2dBzr-S5EtSetDhMyoywJSkSZg6ks5s2dO_gaJpZM4QcJ7S .

deshipu commented 6 years ago

@tannewt I think we can avoid concurrency with the slave if we make a blocking method that simply waits for the start condition and the address on the bus. Then you can execute the rest of your loop, read/write all the data you need, and go back to waiting for a command. The clock is stretched while your code runs, of course. I have done something like this on an attiny (where I needed to avoid interrupts to have precise timings), and it works quite well.

This restricts the use cases a bit, because you can't do anything else while waiting for the command. You could however alleviate it a little, by also having a timeout on it, so then you can run your LED animations or whatever else you need, and wait for the I2C commands instead of doing time.sleep in between the frames.

deshipu commented 6 years ago

An example program could look like this:

import board
import busio

slave_bus = busio.I2CSlave(sda=board.SDA, scl=board.SCL)
register = 0
registers = [0] * 8

while True:
    with slave_bus.wait(0x11, 0x33, timeout=None) as i2c_request:
        if i2c_request.is_write:
           register = slave_bus.read(1)[0]
        else:
           slave_bus.write(registers[register])

Explanation:

First we define which bus we want to use as the slave, and some helper variables. Then in an infinite loop, we wait for a start condition and one of two addresses (we could have any number of them), with an optional timeout. Once there comes a request, we can process it. A request would have a flag telling if it's a write or read request, and the address for which it was received. Then we can read or write bytes to the bus — there is an ACK sent between every two bytes transmitted. At the end of the context manager, a NACK is sent.

tannewt commented 6 years ago

@deshipu I like that! Now we need someone to implement it. ;-)

mik00 commented 6 years ago

Radomir,

I was in a hurry, and so I reverted to C to solve my immediate problem. However, for the record, an implementation per your spec. would have been entirely good enough for my needs. If someone implements it "Yay!". But insert your design rationale prominently so followers can make an early trade-off, and be grateful to you for your expertise. The value of the honest phrase, "We can't do that", is an undervalued commodity. Thanks for your contribution.

On Tue, Nov 28, 2017 at 10:16 AM, Scott Shawcroft notifications@github.com wrote:

@deshipu https://github.com/deshipu I like that! Now we need someone to implement it. ;-)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/adafruit/circuitpython/issues/437#issuecomment-347614393, or mute the thread https://github.com/notifications/unsubscribe-auth/AHHDZ3MSeBox-nHqxSWQUHGCy93ZrSKEks5s7E3zgaJpZM4QcJ7S .

tuupola commented 6 years ago

I am currently evaluating moving to CircuitPython from vanilla MicroPython. Slave I2c would be perfect for my needs too.

marauder37 commented 6 years ago

I'd like to +1 this... I wanted to use my Circuit Playground Express as a tilt sensor for a Raspberry Pi because those are the items on my desk and the CPE is actually cheaper than the real-deal BNO055 plus I can echo the current tilt on the CPE's LEDs. So I want to move a number from the CPE to the Pi maybe 10 times a second and timing isn't critical. It's turned out to be surprisingly hard! This will probably end with me going to the store for an MCP3008 but I2C would be cooler, easier, cheaper, more educational.

@mik00 if your C could be useful for someone else maybe share it? 😄

I'm in no position to move to MicroPython (because noob) but fwiw they have something that does i2c slave: https://docs.micropython.org/en/latest/pyboard/library/pyb.I2C.html#pyb-i2c

tannewt commented 6 years ago

@notro you are looking into this right?

notro commented 6 years ago

Yes, I have something working already, just need to write tests to make sure all cases do work. I'm currently trying to use pytest as a test runner for running tests on the board.

arturo182 commented 6 years ago

Very cool! Let me know if any help is needed with the nRF port :)

notro commented 6 years ago

Progress report:

I'm done with the pytest plugin: https://github.com/notro/pytest-circuitpython

bus.I2CSlave are passing most tests. smbuslave.py pass all tests. ds1307slave.py pass tests. at24slave.py pass tests.

The tests are run on a Raspberry Pi connected to a Feather M0 Express through usb and i2c. The slave is set up through the REPL and tested from Linux over i2c. The hwclock command and the sysfs nvram file are used to test ds1307 and the sysfs eeprom file for the at24c01.

Next up is an ads1015. It uses smbus word transfers so I'm aiming to stress that part of the library.

Here's how one of the test files look: test_ds1307_linux.py

tannewt commented 6 years ago

Awesome! I'm excited to see a PR!

notro commented 6 years ago

Will this go into 3.x or 4.x?

tannewt commented 6 years ago

Up to you if you want to submit to the 3.x branch as well. I think we'll jump to 4.x alphas pretty fast though.

notro commented 6 years ago

I've made a PR: https://github.com/adafruit/circuitpython/pull/1064 Comments are welcome. I haven't got much experience working with I2C devices.

ladyada commented 6 years ago

i think perhaps this can be closed?

dhalbert commented 6 years ago

i think perhaps this can be closed?

Righto! Closed by #1064.