RFC: Hardware API: finalising machine.I2C

C programming, build, interpreter/VM.
Target audience: MicroPython Developers.
Damien
Site Admin
Posts: 647
Joined: Mon Dec 09, 2013 5:02 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by Damien » Fri Oct 21, 2016 1:57 pm

Turbinenreiter wrote: I like it, but it would also be the third time I'll have to update every driver I wrote with a new API. We gotta make this one stick.
I don't mean to make things difficult for users, but the way it is currently is not very consistent across ports. Ideally we would make little to no change to the existing API, so let's see if it really is necessary to change it first.

What API are you using? Pyboard, esp8266 and WiPy all have slightly different I2C APIs so I wonder what methods you use?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: RFC: Hardware API: finalising machine.I2C

Post by dhylands » Fri Oct 21, 2016 3:57 pm

Damien wrote:
dhylands wrote:I see low-level commands for writing start/stop but not for reading ack?
Ok, that's left out. So then how about i2c.writebyte(int) returns the nack/ack value that's read on the bus after sending the single byte? AFAIK the master only needs to read ack after writing a byte.

Similarly, a nack/ack can be written using: i2c.readbyte(int, nack).
ack/nak is always written by the slave and read by the master. Every byte written by the master is ack/nak'd by the device, and every byte read by the master includes the ack/nak from the device.
For multi-byte reads, a NAK is often sent along with the last byte of the transaction. How do we get that status information?
For reads, the master sends the nack at the end, so there's no status info. Or do you mean for multibyte writes?
The master NEVER sends an ACK/NAK, only the slave does that. For multi-byte reads, the slave will typically send a NAK after the last byte to indicate that there will be no further bytes forthcoming.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by deshipu » Fri Oct 21, 2016 4:40 pm

dhylands wrote:But how do you get the data value associated with the NAK (or even the bytes before the NAK) if an exception is raised?
I suppose if you use i2c.readinto(), you will get the value in the buffer even if there was an exception.

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: RFC: Hardware API: finalising machine.I2C

Post by Turbinenreiter » Fri Oct 21, 2016 5:48 pm

Damien wrote:
Turbinenreiter wrote: I like it, but it would also be the third time I'll have to update every driver I wrote with a new API. We gotta make this one stick.
I don't mean to make things difficult for users, but the way it is currently is not very consistent across ports. Ideally we would make little to no change to the existing API, so let's see if it really is necessary to change it first.

What API are you using? Pyboard, esp8266 and WiPy all have slightly different I2C APIs so I wonder what methods you use?
I know and understand, I just wanted to point out that it is very important to get this right.

I'm using all three (although I stopped using the WiPy) and the new atmel-samd port from Adafruit. The methods I use are pretty much only mem_read and mem_read (or their equivalence on the specific port, like readfrom_mem and writeto_mem. I also use scan, but only interactively for debugging.

I think the I2CEndpoint is a very good design. My drivers would then simply inherit from it and take an I2C bus object and i2c address as argument on creation.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by deshipu » Fri Oct 21, 2016 5:58 pm

Turbinenreiter wrote: I think the I2CEndpoint is a very good design. My drivers would then simply inherit from it and take an I2C bus object and i2c address as argument on creation.
If you opt for composition instead of inheritance (keeping the endpoint as your object's attribute), then the same code can be used for i2c and spi based devices, and you can even pass it a mock object in your tests, and later verify that the right methods were called with the right arguments. Or use a version of the endpoint that adds additional logging, for easier debugging.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by deshipu » Fri Oct 21, 2016 6:01 pm

I assume that the spi and possibly other protocols would have similar "endpoint" objects, with identical methods on them.

One problem with creating the endpoint outside of the driver's code is that the user now needs to know the default i2c address of the device.

tannewt
Posts: 51
Joined: Thu Aug 25, 2016 2:43 am

Re: RFC: Hardware API: finalising machine.I2C

Post by tannewt » Sat Oct 22, 2016 1:33 am

Damien wrote:Following from http://forum.micropython.org/viewtopic.php?f=3&t=2545, here we can discuss machine.I2C. This is really critical to get right, and make sure all ports implement it correctly, because it forms the basis of many drivers (eg LCD, env sensors, etc).

I think there should be a low-level I2C class which provides complete functionality and is relatively user-friendly, but at the same time being minimal. It should allow to construct I2C transactions piece-wise for cases where the data to send comes from different buffers (eg for controlling an LCD). It should allow to do (almost) everything without allocating on the heap.

Constructor:

Code: Select all

I2C(id, scl, sda, freq)
- id is the peripheral id, with -1 meaning software implementation
- scl, sda are pin objects
- freq is the frequency of the bus to use (could be called baudrate or baud)
Control/config methods:

Code: Select all

i2c.config(...) # general config function to get/set values, eg i2c.config(freq=100000)
i2c.close() # shut down the peripheral
Core transfer methods:

Code: Select all

i2c.start(addr, dir) # dir is one of i2c.READ, i2c.WRITE
i2c.write(buf)
i2c.writebyte(int)
i2c.readinto(buf)
i2c.readbyte()
i2c.stop()
Composite helper methods for common transactions:

Code: Select all

i2c.writeto(addr, buf) # = start(addr, i2c.WRITE); write(buf); stop()
i2c.readfrom_into(addr, buf) # = start(addr, i2c.READ); readinto(buf); stop()
i2c.write_readinto(addr, wr_buf, rd_buf) # = start(addr, i2c.WRITE); write(wr_buf); start(addr, i2c.READ); readinto(buf); stop()
In addition to I2C there would be an I2CEndpoint class which is purely for convenience, but is probably going to be used much more than the low-level I2C class itself.

Endpoint constructor:

Code: Select all

I2CEndpoint(i2c, addr, regaddrsize=8)
- i2c is a bus
- addr is the device address
- regaddrsize is the number of bits (8/16/32) needed to specify an i2c register address
Endpoint attributes:

Code: Select all

ep.i2c # get the underlying i2c bus
ep.addr # get the device address
ep.regaddrsize # get the register address size
Endpoint methods:

Code: Select all

ep.write(buf) # = i2c.writeto(ep.addr, buf)
ep.read(n) # = buf=bytearray(n); i2c.readfrom_into(ep.addr, buf); return buf
ep.readinto(buf) # = i2c.readfrom_into(ep.addr, buf)
ep.reg_write(regaddr, int_value) # select register and write value
ep.reg_read(regaddr) # select register and read value, returns an integer
What has the experience been with the peripheral ID? I know some ports don't have it and it doesn't seem very portable to me. Seems like it'd be better to let the port's implementation match up a hardware peripheral to the given pins where its a different MCU on a board with a standard form factor like Arduino Uno or Adafruit Feather.

In addition to close() why not add Context Manager support to match how files and network sockets are used in Python? ContextManagers are super nice because they deinit even when a KeyboardInterrupt happens.

For the helper methods and the convenience class why not break it out of machine and ship it separately? That has the advantage of reducing the ROM footprint for uses that aren't I2C. (There can even be multiple friendlier versions that drivers can choose from.)

Breaking out software I2C makes sense to me for the same reason too, it saves ROM space at the expense of RAM when its needed.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: RFC: Hardware API: finalising machine.I2C

Post by dhylands » Sat Oct 22, 2016 4:11 am

deshipu wrote:I assume that the spi and possibly other protocols would have similar "endpoint" objects, with identical methods on them.

One problem with creating the endpoint outside of the driver's code is that the user now needs to know the default i2c address of the device.
The driver could make the default address available as a constant or function. The outside code could then use it if its appropriate (it might not be - since many devices have selectable addresses)

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: RFC: Hardware API: finalising machine.I2C

Post by dhylands » Sat Oct 22, 2016 7:00 am

I think that the low-level API is fine for bit-banging but annoying for supporting real peripherals. I think that it also makes supporting things like DMA much more difficult.

It basically means that if you want to do DMA transfers you have to "fake" the low level API. You need to buffer everything until the end and then DMA out the whole thing. Or not do DMA if somebody chooses to use the low level API, and only do DMA if the higher-level APIs are used. Which means that the high level APIs will no longer just be the low-level APIs concatenated together.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by deshipu » Sat Oct 22, 2016 9:36 am

Would it be possible to come up with a set of high-level APIs that both map well to the underlying port function calls, and provide reasonably elastic ways to support the hardware that is out there?

Perhaps it would make sense to only have the low-level API on the software I2C implementation, and go for the high-level API for the hardware ones, so that we can still use the low-level API when we have a device with very special needs, but don't loose the efficiency and convenience when handling common cases?

Post Reply