Join GitHub today
RFC: defining an I2C slave class and behaviour #3935
I2C slave mode is an often-requested feature. See eg #3680 and #3858, and #3114 for a previous discussion that was tied to esp8266 implementation. It would be good to define an I2C slave class and how it would behave, and then implementation for various ports could follow.
stm32 recently got some I2C slave code and the interface is in this header: https://github.com/micropython/micropython/blob/master/ports/stm32/i2cslave.h . In this C implementation there are 4 external functions that should be provided and which are essentially callbacks from the low-level I2C slave driver:
// the slave's address was matched, and the master requested a read/write (via rw param) // return 0 to ack address or 1 to nack it int i2c_slave_process_addr_match(int rw); // master sent a byte, slave received it (val param) // return 0 to ack the byte or 1 to nack it int i2c_slave_process_rx_byte(uint8_t val); // master stopped sending data via a stop or repeated start condition void i2c_slave_process_rx_end(void); // master requests data, slave needs to send it // return the byte to send back to the master uint8_t i2c_slave_process_tx_byte(void);
This describes a very low level interface, at the byte level. With a bus at 100kHz bytes are transferred at about 10kHz. Python code might be able to respond at this rate to individual bytes, but only on fast hardware. And a 400kHz SCL will make it more difficult to respond to individual bytes. So, while the above interface allows to implement (almost) any I2C device, it's not really a solution for MicroPython.
We need to define a I2C slave API at a higher level than bytes. A callback for each full bus transaction would be the way to go.
The most obvious I2C behaviour to implement is a memory-mapped device, eg
mem_buf = bytearray(32) i2c_mem = I2CSlaveMemory(scl=scl, sda=sda, addr=67, mem_addr_len=8, mem=mem_buf) # master can now access the mem_buf array while True: mem_buf += 1 time.sleep(1)
For simple cases that doesn't even need a callback. But
Other types of high-level devices include:
In addition to defining the tx/rx behaviour (memory/stream/packet/etc) it's also important to consider whether a callback interface should be used for events as they happen, or something more like polling (eg like sockets). For polling the I2C slave device would have methods
With FIFO buffering and a UART-like API (with an ioctl) it could be used with uasyncio StreamReader and StreamWriter classes. Default buffer sizes would need to be capable of being overridden in the constructor given uasyncio's lethargic approach to I/O polling :)
Yes exactly. I think it'd be a high priority item to make sure it can work with uasyncio. But not only that, it should also work with other execution models like IRQs/callbacks.
IRQ/callback is best option, and it gonna be really nice to have i2c as slave on some devices as esp8266, I currently use an atmega processors with arduino id cuz it is only thing that support i2c slave mode and works stable. Micropython, lua-nodemcu do not support slave mode.