PyB-to-PyB using I2C?
-
- Posts: 68
- Joined: Sat May 03, 2014 11:31 pm
PyB-to-PyB using I2C?
I wonder if anyone has implemented a simple network using I2C between two or more PyBoards, either multimaster or single master? I have done this before in the PIC18 world, and was going to give it a try. So far I have only used the PyB as an I2C master. The slave side is always much trickier, I wondered if someone had already tried this (or something similar- maybe the PyB as a slave to another typ of processor)? Searching did not reveal anything.
-
- Posts: 68
- Joined: Sat May 03, 2014 11:31 pm
Re: PyB-to-PyB using I2C?
As a follow up, it does not seem there is any way to make the slave mode be interrupt driven. It would be nice if it could be a stream that could be polled, using the select module, like the uart.
-
- Posts: 68
- Joined: Sat May 03, 2014 11:31 pm
Re: PyB-to-PyB using I2C?
I set up two Pyboards, one as a slave
i2c0 = I2C(1, I2C.SLAVE) # create and init as a slave
i2c0.init(I2C.SLAVE, addr = 0x55) # init as a slave
print("Slave")
The other as a master to look for it:
i2c = I2C(1, I2C.MASTER) # create and init as a master
i2c.init(I2C.MASTER, baudrate=10000) # init as a master
print("Master")
print("Scanning I2C1")
addr_list=[]
addr_list = i2c.scan()
print("Devices found at:",addr_list)
But it does not find it:
>>>
PYB: sync filesystems
PYB: soft reboot
Master
Scanning I2C1
Devices found at: []
I see activity on the I2C lines using a scope, it all looks normal. I reduced the rate to 10kHz just to be sure.
I would have thought that this is the simplest test to just verify that the slave can be found. I have not been able to find a full example where the Pyb is used as a slave. Can anyone suggest an even more fundamental test?
i2c0 = I2C(1, I2C.SLAVE) # create and init as a slave
i2c0.init(I2C.SLAVE, addr = 0x55) # init as a slave
print("Slave")
The other as a master to look for it:
i2c = I2C(1, I2C.MASTER) # create and init as a master
i2c.init(I2C.MASTER, baudrate=10000) # init as a master
print("Master")
print("Scanning I2C1")
addr_list=[]
addr_list = i2c.scan()
print("Devices found at:",addr_list)
But it does not find it:
>>>
PYB: sync filesystems
PYB: soft reboot
Master
Scanning I2C1
Devices found at: []
I see activity on the I2C lines using a scope, it all looks normal. I reduced the rate to 10kHz just to be sure.
I would have thought that this is the simplest test to just verify that the slave can be found. I have not been able to find a full example where the Pyb is used as a slave. Can anyone suggest an even more fundamental test?
Re: PyB-to-PyB using I2C?
I just wanted to acknowledge that I just tried a similar test to what Gordon was attempting - I tried to set up I2C(1) as a master and I2C(2) as a slave on the same pyboard and get the two interfaces talking to each other - with no success.
I didn't dig too deep into the problem yet - I'm planning to take a look with my logic analyzer tomorrow to see if I can make any sense of it.
I will note that I don't recall seeing any discussions of users configuring the pyboard as an I2C slave; I tend to follow the I2C discussions here so I would expect to remember it. It's quite possible that the slave configuration isn't well tested.
-Bryan
I didn't dig too deep into the problem yet - I'm planning to take a look with my logic analyzer tomorrow to see if I can make any sense of it.
I will note that I don't recall seeing any discussions of users configuring the pyboard as an I2C slave; I tend to follow the I2C discussions here so I would expect to remember it. It's quite possible that the slave configuration isn't well tested.
-Bryan
Re: PyB-to-PyB using I2C?
I finally managed to get a basic master-slave I2C transaction working across two pyboards, but it isn't terribly obvious.
The basic hurdle is that the current implementation of I2C in uPy only supports blocking / polling mode for data transfers (i.e. interrupts and DMA are not supported.) As a result, you can't currently have I2C transfers running in the background.
This is usually not a huge problem for an I2C master, the expectation is that you can initiate a transfer when you want, the slave will respond almost immediately, and you can go on to do your next task.
However, when MicroPython initializes an I2C port in slave mode, it won't even acknowledge its address being called unless it is currently executing a 'send()' or 'recv()' method - and if it is currently executing one of these methods, it can't also execute a corresponding method on the other I2C port at the same time (because each method is blocking). That is why I couldn't make I2C(1) transmit to I2C(2) on the same pyboard.
It also explains why Gordon couldn't get the I2C slave on one pyboard to respond to the 'scan()' call by the I2C master on the other board - the I2C slave only acknowledges if it is currently executing a 'send()' or 'recv()' and either of these methods will get confused if the master is just calling out addresses without transferring data. In fact, at this point I think that it would be fairly hard to program the I2C slave in uPy without having a pretty good idea ahead of time what the calls from the master are going to be (but this may just be my own shortcoming )
Anyway, I was able to make this work, connecting I2C(1) on each pyboard - (note that default slave address is 0x12):
This isn't a strict cut-and-paste, obviously, but I think that this is what worked for me.
Edited to correct the code example - slave send() must be set before master recv(), because the data clock is driven only by the master.
-Bryan
The basic hurdle is that the current implementation of I2C in uPy only supports blocking / polling mode for data transfers (i.e. interrupts and DMA are not supported.) As a result, you can't currently have I2C transfers running in the background.
This is usually not a huge problem for an I2C master, the expectation is that you can initiate a transfer when you want, the slave will respond almost immediately, and you can go on to do your next task.
However, when MicroPython initializes an I2C port in slave mode, it won't even acknowledge its address being called unless it is currently executing a 'send()' or 'recv()' method - and if it is currently executing one of these methods, it can't also execute a corresponding method on the other I2C port at the same time (because each method is blocking). That is why I couldn't make I2C(1) transmit to I2C(2) on the same pyboard.
It also explains why Gordon couldn't get the I2C slave on one pyboard to respond to the 'scan()' call by the I2C master on the other board - the I2C slave only acknowledges if it is currently executing a 'send()' or 'recv()' and either of these methods will get confused if the master is just calling out addresses without transferring data. In fact, at this point I think that it would be fairly hard to program the I2C slave in uPy without having a pretty good idea ahead of time what the calls from the master are going to be (but this may just be my own shortcoming )
Anyway, I was able to make this work, connecting I2C(1) on each pyboard - (note that default slave address is 0x12):
Code: Select all
MASTER: SLAVE:
>>> from pyb import I2C
>>> i2cM = I2C(1, I2C.MASTER)
>>> i2cM.init(I2C.MASTER)
>>> from pyb import I2C
>>> i2cS = I2C(1, I2C.SLAVE)
>>> i2cS.init(I2C.SLAVE)
>>> i2cS.recv(3, timeout=-1) ## no timeout
>>> i2cM.send('ABC', addr=0x12)
b'ABC'
>>> i2cS.send('ABC', timeout=-1)
>>> i2cM.recv(3, addr=0x12)
b'ABC'
Edited to correct the code example - slave send() must be set before master recv(), because the data clock is driven only by the master.
-Bryan
-
- Posts: 68
- Joined: Sat May 03, 2014 11:31 pm
Re: PyB-to-PyB using I2C?
Bryan,
Thanks. Yes, that seems to work for me as well with two pyboards. Also with my address of 0x55, not just the default. I suppose it might allow for some sort of primitive communication, provided the slave process can stand being blocked for some period of time. Something like this:
while true:
data = []
data = i2cS.recv(3, timeout=90) ## or whatever number of msecs uses up spare time
if data != []:
do_communications() ## send back data, receive commands
else:
do_tasks() ## read ADCs, control motors, fill buffers, whatever. Might take 10 msec
So the process "hangs" for 90 msec, giving the master time to grab it and do the necessary. Then does its main tasks. Can anyone see a better way to do this?
Unless the slave mode can be interrupt driven, it is all but useless, I guess, unless it is okay for the slave to spend a good deal of its time hanging out waiting to be addressed.
Gordon
Thanks. Yes, that seems to work for me as well with two pyboards. Also with my address of 0x55, not just the default. I suppose it might allow for some sort of primitive communication, provided the slave process can stand being blocked for some period of time. Something like this:
while true:
data = []
data = i2cS.recv(3, timeout=90) ## or whatever number of msecs uses up spare time
if data != []:
do_communications() ## send back data, receive commands
else:
do_tasks() ## read ADCs, control motors, fill buffers, whatever. Might take 10 msec
So the process "hangs" for 90 msec, giving the master time to grab it and do the necessary. Then does its main tasks. Can anyone see a better way to do this?
Unless the slave mode can be interrupt driven, it is all but useless, I guess, unless it is okay for the slave to spend a good deal of its time hanging out waiting to be addressed.
Gordon
Re: PyB-to-PyB using I2C?
Or we can look at it as a use-case to motivate the implementation of interrupt- or DMA-driven data transfers in I2C. And there is no reason to think that it can't be done; the DAC and SPI classes already implement DMA transfers, and the HAL layer provides the base functions to use I2C in interrupt or DMA mode. I suspect that I2C was implemented with blocking functions initially because that was easiest, and until now it hasn't been a high priority to improve it.Gordon_Hardman wrote:Unless the slave mode can be interrupt driven, it is all but useless, I guess, unless it is okay for the slave to spend a good deal of its time hanging out waiting to be addressed.
I don't quite feel that my own C skills are up to the challenge just yet - but ironically, working in MicroPython has me looking at C code for the first time in many years (I'm certainly not complaining, its just another opportunity to learn.)
-Bryan
-
- Posts: 68
- Joined: Sat May 03, 2014 11:31 pm
Re: PyB-to-PyB using I2C?
Bryan,
Yes, understood! Maybe the simplest would be to be able to set a callback if the slave's address is recognized, and then do the rest of the code in Python. I did this on a PIC18 using Swordfish a couple of years back, and it worked fine. My c code skills are probably even more lamentable than yours, I am afraid!
I wonder if setting a callback on the I2C could be done using inline assembly?
Gordon
Yes, understood! Maybe the simplest would be to be able to set a callback if the slave's address is recognized, and then do the rest of the code in Python. I did this on a PIC18 using Swordfish a couple of years back, and it worked fine. My c code skills are probably even more lamentable than yours, I am afraid!
I wonder if setting a callback on the I2C could be done using inline assembly?
Gordon
-
- Posts: 68
- Joined: Sat May 03, 2014 11:31 pm
Re: PyB-to-PyB using I2C?
From the STM32F405 data sheet:
"Up to three I²C bus interfaces can operate in multimaster and slave modes. They can
support the Standard-mode (up to 100 kHz) and Fast-mode (up to 400 kHz) . They support
the 7/10-bit addressing mode and the 7-bit dual addressing mode (as slave). A hardware
CRC generation/verification is embedded.
They can be served by DMA and they support SMBus 2.0/PMBus."
DMA- that's the way to do it!
"Up to three I²C bus interfaces can operate in multimaster and slave modes. They can
support the Standard-mode (up to 100 kHz) and Fast-mode (up to 400 kHz) . They support
the 7/10-bit addressing mode and the 7-bit dual addressing mode (as slave). A hardware
CRC generation/verification is embedded.
They can be served by DMA and they support SMBus 2.0/PMBus."
DMA- that's the way to do it!
Re: PyB-to-PyB using I2C?
It occurs to me that there is a limitation within the current uPy API from the point of view of implementing an I2C slave - the 'recv()' method accepts either a data buffer or an integer indicating how many bytes to accept. However, as a slave you don't necessarily know how many bytes you are going to receive, and if you specify an oversize buffer then you are waiting until the buffer gets filled or until you time out - it would be much better if the recv() method returned the received data when the stop condition is detected on the I2C bus, but that isn't how it currently works.
There is also the issue that we don't have an I2C.listen() method - this would be a method that could keep the I2C port in slave mode actively listening for its address to be called while running in the background, so that it can acknowledge bus requests appropriately.
That said, I don't know how to do this or if the HAL layer supports it - I went back to a discussion I had with @dhylands on Github back in July over a different I2C issue - there we concluded that the lack of a combined generic send/receive function put a limitation on what we could implement in MicroPython - https://github.com/micropython/micropyt ... t-48668440
-Bryan
There is also the issue that we don't have an I2C.listen() method - this would be a method that could keep the I2C port in slave mode actively listening for its address to be called while running in the background, so that it can acknowledge bus requests appropriately.
That said, I don't know how to do this or if the HAL layer supports it - I went back to a discussion I had with @dhylands on Github back in July over a different I2C issue - there we concluded that the lack of a combined generic send/receive function put a limitation on what we could implement in MicroPython - https://github.com/micropython/micropyt ... t-48668440
-Bryan