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

RFC: Hardware API: finalising machine.I2C

Post by Damien » Fri Oct 21, 2016 7:17 am

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

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 7:46 am

I see low-level commands for writing start/stop but not for reading ack?

For example, how would you do an SMBus Quick command? which is a Start/Address/RW/wait-for-ack/Stop

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 an endpoint, what about devices which have no registers? (in reference to the regaddrsize parameter?) Or is this a case where you would just use the low-level API? For example the PCF8574 doesn't have registers.

pfalcon
Posts: 1155
Joined: Fri Feb 28, 2014 2:05 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by pfalcon » Fri Oct 21, 2016 7:55 am

i2c.writebyte(int)
What if you need to write 2-byte value? 4-byte? With different byte order? This problem is not specific to I2C, but pertinent to any stream (or stream-like) object.
Awesome MicroPython list
Pycopy - A better MicroPython https://github.com/pfalcon/micropython
MicroPython standard library for all ports and forks - https://github.com/pfalcon/micropython-lib
More up to date docs - http://pycopy.readthedocs.io/

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 8:13 am

So

Code: Select all

ep.reg_write()
would replace

Code: Select all

I2C.readfrom_mem()
?
And the class for the I2C device would inherit from I2CEndpoint?

Reading and writing bytes definitely should be able to do multiple bytes. It's quite common to have to read two bytes from two successive registers sensors. I.e. this:

Code: Select all

MSB, LSB = ep.reg_read(regaddr, no_of_bytes)
should be possible.

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.

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 8:35 am

dhylands wrote:I see low-level commands for writing start/stop but not for reading ack?
I think that at the moment you get an exception whenever there is an unexpected NAK.

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 8:41 am

But how do you get the data value associated with the NAK (or even the bytes before the NAK) if an exception is raised?

Often, the very last byte is NAKed as an indication of "end of transmission". This is fine if you know ahead of time that you're going to be reading some number of bytes, but what if you don't know ahead of time?

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 9:12 am

So one problem that was discovered with the low-level API so far is that it makes the implementation a little bit tricky on some of the ports. because the libraries for them are a little bit higher level -- in particular, they don't allow sending a single "start" or "stop" condition, but instead have a version of the write function that doesn't send a stop at the end -- similar to how it is done in Micro:bit.

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 12:59 pm

pfalcon wrote:
i2c.writebyte(int)
What if you need to write 2-byte value? 4-byte? With different byte order? This problem is not specific to I2C, but pertinent to any stream (or stream-like) object.
Sorry, maybe it wasn't clear, but this command just sends 8-bits on the bus (no start/stop bits etc). If you want to send 2-byte value you do writebyte twice in a row with the relevant int arguments, and so on. The idea to have it is to eliminate the need for temporary bytearrays in some cases. (But see also my follow up comment below about the return value of this method.)

pfalcon
Posts: 1155
Joined: Fri Feb 28, 2014 2:05 pm

Re: RFC: Hardware API: finalising machine.I2C

Post by pfalcon » Fri Oct 21, 2016 1:07 pm

Damien wrote:
pfalcon wrote:
i2c.writebyte(int)
Sorry, maybe it wasn't clear, but this command just sends 8-bits on the bus (no start/stop bits etc). If you want to send 2-byte value you do writebyte twice in a row with the relevant int arguments, and so on.
8 times in row for 64-bit value, with shifts and masking by yourself, no?

Likewise, if it wasn't clear, I'd like to call for a general solution of reading/writing encoded numeric values to stream-like objects, not just for a particular stream-like object (I2C connection) and not just for a single data type (a byte). With our size constrained, we really can't afford to solving it separately for each object and data type combination. I posted https://github.com/micropython/micropython/issues/2543
Awesome MicroPython list
Pycopy - A better MicroPython https://github.com/pfalcon/micropython
MicroPython standard library for all ports and forks - https://github.com/pfalcon/micropython-lib
More up to date docs - http://pycopy.readthedocs.io/

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:07 pm

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).
For example, how would you do an SMBus Quick command? which is a Start/Address/RW/wait-for-ack/Stop
AFAIK this would be: i2c.start(addr, i2c.READ/i2c.WRITE); i2c.stop()

Even with the current implementation in esp8266 you can do: i2c.start(); i2c.write(bytearray([addr << 1])); i2c.stop()
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?
For an endpoint, what about devices which have no registers? (in reference to the regaddrsize parameter?) Or is this a case where you would just use the low-level API? For example the PCF8574 doesn't have registers.
You would just not use the register methods (and would leave the regaddrsize parameter at it's default value).

Post Reply