Can Multiple I2C Sensors Be Run Simultaneously

Hi,

One of the useful features of the Propeller 1 is that you can interogate the status or results of 8 different sensors simultaneously using a different Cog for each sensor. The Time-Of-Flight Distance Measuring Sensor VL6180X ( https://www.adafruit.com/products/3316 ) has a fixed I2C address. I am new to the I2C world, but it appears that the Propeller 1 has only 1 set of pins that can be used for I2C devices. So am I correct that you could not have 8 VL6180X sensors on 8 different Cogs so that you can obtain 8 distance measurements simultaneoulsy?

I advance, thank you and happy holidays!
«1

Comments

  • 53 Comments sorted by Date Added Votes
  • Any pins can be used for i2c.

    Here are a few drivers in the OBEX:

    http://obex.parallax.com/search/i2c

    Infernal Machine
  • Indeed, you could have up to 16 I2C buses on the Propeller, depending on whether or not your code requires a dedicated cog :)
    David
    PropWare: C++ HAL (Hardware Abstraction Layer) for PropGCC; Robust build system using CMake; Integrated Simple Library, libpropeller, and libPropelleruino (Arduino port); Instructions for Eclipse and JetBrain's CLion; Example projects; Doxygen documentation
  • PublisonPublison Posts: 9,278
    edited December 2016 Vote Up0Vote Down
    With only one address available, one might be able to bring up several i2c objects in different Cogs by giving the objects different names, (I2C_1, I2C_2). You would also have to change some code, (example, variable name), in each I2C object, because, I believe, it will only load one instance of the object, and not separate code for each object, which might cause problems.
    Infernal Machine
  • Since the VL6180X from Adafruit has a fix i2C address (0x29), you usually could only have one on a single i2C bus path. Typically each device on the i2C bus must have a separate address assigned to avoid address conflicts, however it appears there is a way to have multiple VL6180X devices on a single i2C address but I am not sure if this applies to the Propeller as well.

    st.com/content/ccc/resource/technical/document/application_note/b4/f0/79/ec/ca/54/45/07/DM00114403.pdf/files/DM00114403.pdf/jcr:content/translations/en.DM00114403.pdf

    But, as DavidZ mentioned, you could have up to 16 i2C buses on the Propeller, which is cool, so each device could have the same i2C address but on a separate bus.

    If you are new to i2C, Parallax has a pretty good DIY i2C Learn reference you might want to have a look-see at:
    learn.parallax.com/tutorials/language/propeller-c/propeller-c-simple-protocols/diy-i2c
  • The Prop can create eight i2c busses by using 9 pins. One pin is the shared scl clock, and 8 pins are separate sda's. Or vice versa, one shared sda and 8 separate scl. Use pullup resistors and an i2c driver that needs the pullups.
  • Unless all eight I2C drivers are in the same cog, sharing SCL or SDA will require the use of locks to avoid bus contention.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • PublisonPublison Posts: 9,278
    edited December 2016 Vote Up0Vote Down
    I knew there was more to it. :)
    Infernal Machine
  • @robotics, as you can see from these posts, you can do lots with the Propeller. As you might have also noticed, various examples (the i2c objects in the obex, the Learn tutorials from learn.parallax.com) come in different languages. If you want to use an existing solution, you'll have to let us know which language you are using (or want to use) so that we can narrow down our examples to that language. If you want to roll your own solution, the possibilities are endless! :D
    David
    PropWare: C++ HAL (Hardware Abstraction Layer) for PropGCC; Robust build system using CMake; Integrated Simple Library, libpropeller, and libPropelleruino (Arduino port); Instructions for Eclipse and JetBrain's CLion; Example projects; Doxygen documentation
  • Tracy AllenTracy Allen Posts: 5,933
    edited December 2016 Vote Up0Vote Down
    I was thinking that "simultaneous" might mean that a few milliseconds apart in sequence would qualify as "close enough". In that case, one cog would step through the raw acquisition process while other cogs do processing and UI. No locks needed in that case. Anyway, locks too by nature enforce non-oversimultaneous readings.

    One possible solution using 9 pins scheme: From the VL6180x data sheet, one command starts the acquisition process, and another command reads the returned data. (section 2.4.1, single shot range/ALS operation) Therefore it might be possible to send the same "start" command to all of them simultaneously, and then read back the data one at a time when they are finished. There is a status flag that can be read to ascertain the busy/done state.

    A salient point about "simultaneous" is that the range acquisition time depends mightily on both the range and on the reflectivity of the object. (Table 7 in the data sheet). It is roughly 0.1 to 10 milliseconds when an object is well within the field of view. So even if they are started at the same time, the finish times will be staggered. How often do you expect to take readings?
  • I do appreciate everyone's input on this propeller application.

    In regards to DavidZemon's question, I would prefer to program this in Spin. In regards to Tracy Allen's question, I would like to take readings as frequent as possible, so in regard to Table 7 , the worst case longest time period would be used.
  • The VL6180X takes .1 sec to get a good reading - at least. Depends on the accuracy needed. This is a looong time in I2C, so bus delays should not count much. I have found a way to re-address the chips as part of the initialization. You just need to have each reset/enable pin tied to a (different) resistor-capacitor to cause each chip to come online (and get re-addressed) at a few mSec apart. After this initialization they can be addressed individually by their own address. No need to spend a lot of I/O as their application note idea requires.

    Erlend
    21st century - when everything changes
    "Better with a DAT and a COG than with a CAT and a DOG"
  • Erland, that's a nice scheme, to use that readdressing capability. Adafruit board calls that pin SHDN, right? the one that ST calls gpio0 or enable? That chip sure enough has a lot of configuration registers.


  • That chip is a configuration register night mare! I do not know about the Adafruit version, but you are right about the GPIO0. I also got that scheme working by using the power supply pin, but the capacitors get a bit large.

    Erlend
    21st century - when everything changes
    "Better with a DAT and a COG than with a CAT and a DOG"
  • I have installed Erlend's VL6180X driver and have successfully hooked up a VL6180X sensor and tested it. The problem arises that when a second Cog is started to run a second VL6180X sensor which is located on a separate I2C bus, only one sensor is recognized by the propeller. In Publison's December post, it was mentioned that there may be a problem using two I2C buses since each VL6180X sensor has the same address even though two distinct buses are used. I don't know if this is the problem since I thought that the propeller chip runs each Cog separately from each other to eliminate such problems. Your suggestions are appreciated, and again my thanks to Erlend and John Abshier for sharing their VL6180X drivers.
  • PublisonPublison Posts: 9,278
    edited January 1 Vote Up0Vote Down
    Do you run two instances of the I2C object?

    What does your code look like now? Seeing the code would better help in tracking down the problem.
    Infernal Machine
  • Peter JakackiPeter Jakacki Posts: 6,197
    edited January 1 Vote Up0Vote Down
    I like Erlend's idea as I see that this chip allows an additional slave address to be configured unlike many other I2C devices. But rather than different value capacitors you could perhaps use a cheap shift register with its clock and data tied to the same I2C lines but with the load clock driven from a dedicated I/O. That way you could chain as many shift registers as needed although one would be all that would normally be required and very precisely bring each sensor out of reset and assign a unique slave address in turn. So that would only require one additional I/O in addition to the two I/O for a common I2C bus, so three I/O altogether for as many sensors as you could use and no fiddly timing caps.

    The clock and data activity to the shift register at reset would not be recognized by any I2C device as it does not generate a start condition, therefore all activity is ignored.
  • I added some basic test code to the driver which is attached. The result of running the code was that the sensor run on Cog 0 worked, while the second sensor was ignored by the second running Cog. Thank you for your assistance.
  • The I2C object uses DAT variables to specify pin numbers. Any code calling the I2C object will therefore be using the same pin numbers, i.e. same I2C bus. If you want to avoid that (if you want to run two I2C busses) you need to trick the system into loading two instances of this object. You achieve this by naming the two objects different (e.g. I2Cdriver and I2Cdriver2) in the parent code, and change a bit of the source code in one of the objects (as I have prepared for in the 'ThisObjectInstance').
    DAT
              PINscl              LONG    1                            'Use DAT variable to make the assignment stick for later calls to the object, and optionally        'Use DAT variable to make the assignment stick for later calls to the object 
              PINsda              LONG    2                            'assign to default pin numbers. Use Init( ) to change at runtime. Best for many chips same one bus. 'and assign to default pin numbers Use Init( ) to change at runtime
              BusInitialized      LONG    FALSE                        'If this is not desired, change from defining PINmosi etc. as DAT to VAR, and                       
                                                                       'assign value to them in Init by means of 'PINmosi:= _PINmosi' etc. instead. Best when many busses.
                                                                       
              ThisObjectInstance  LONG    1                            'Change to separate object loads for different physical buses
    
    

    Hope this helps,

    Erlend
    21st century - when everything changes
    "Better with a DAT and a COG than with a CAT and a DOG"
  • PublisonPublison Posts: 9,278
    edited January 2 Vote Up0Vote Down
    Erlend wrote: »
    The I2C object uses DAT variables to specify pin numbers. Any code calling the I2C object will therefore be using the same pin numbers, i.e. same I2C bus. If you want to avoid that (if you want to run two I2C busses) you need to trick the system into loading two instances of this object. You achieve this by naming the two objects different (e.g. I2Cdriver and I2Cdriver2) in the parent code, and change a bit of the source code in one of the objects (as I have prepared for in the 'ThisObjectInstance').
    DAT
              PINscl              LONG    1                            'Use DAT variable to make the assignment stick for later calls to the object, and optionally        'Use DAT variable to make the assignment stick for later calls to the object 
              PINsda              LONG    2                            'assign to default pin numbers. Use Init( ) to change at runtime. Best for many chips same one bus. 'and assign to default pin numbers Use Init( ) to change at runtime
              BusInitialized      LONG    FALSE                        'If this is not desired, change from defining PINmosi etc. as DAT to VAR, and                       
                                                                       'assign value to them in Init by means of 'PINmosi:= _PINmosi' etc. instead. Best when many busses.
                                                                       
              ThisObjectInstance  LONG    1                            'Change to separate object loads for different physical buses
    
    

    Hope this helps,

    Erlend

    I think I brought that up in a previous post in this thread, but the OP might not have understood it.
    With only one address available, one might be able to bring up several i2c objects in different Cogs by giving the objects different names, (I2C_1, I2C_2). You would also have to change some code, (example, variable name), in each I2C object, because, I believe, it will only load one instance of the object, and not separate code for each object, which might cause problems.
    Infernal Machine
  • I appreciate the additional posts and especially the enhanced explanation by Erlend and Publison. I think I understand it better now. I'll get to work and try programming it in. Best regards
  • Wouldn't it be simpler just to put the pin-numbers in VAR variables? At least that way you would not be loading separate copies of the actual code, as well.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • The approach I use is to create an array of I2C objects. Let's say I use the jm_i2c object, which has variables in dat:
    con
    ' pin assignments
      Inst_SDA        = 8                                   ' I²C, Accel, Gyro
      Inst_SCL        = 9                                   ' I²C, Accel, Gyro
      Multi_SCL       = 22                                  ' I²C Fuel gauge 1, EEPROM, RTC 
      Multi_SDA       = 23                                  ' I²C Fuel gauge 1, EEPROM, RTC 
      Multi2_SCL      = 28                                  ' I²C Fuel Gauge 2                                                       
      Multi2_SDA      = 29                                  ' I²C Fuel Gauge 2                                                      
    ' array indices
      Inst            = 0                                   ' I²C array index        
      Multi           = 1                                   ' I²C array index        
      Multi2          = 2                                   ' I²C array index        
    
    obj
      jm_i2c[3] : "jm_i2c"                                  
                                                                                                 
    pub
    ' initialize
      jm_i2c[inst].setupx(Inst_SCL, Inst_SDA)               
      jm_i2c[multi].setupx(Multi_SCL, Multi_SDA)            
      jm_i2c[multi2].setupx(Multi2_SCL, Multi2_SDA)         
    
    ' use
      jm_i2c[inst].start                                                                
      <read or write>
      jm_i2c[inst].stop
    
      jm_i2c[multi].start                                                                
      <read or write>
      jm_i2c[multi].stop
    
      jm_i2c[multi2].start                                                                
      <read or write>
      jm_i2c[multi2].stop
    
  • ErlendErlend Posts: 543
    edited January 3 Vote Up0Vote Down
    I did a lot of back and forth thinking before I decided how to implement the I2C. I do not think there is a one best way. It depends if you plan for multiple busses, and single bus is the exception, or if you plan for single bus, and multiple busses is the exception. The latter case applies for me, so therefore I chose a way which I believe is most elegant for that purpose. If you think multiple busses will be the rule, I believe the most elegant way is to let pin numbers be VAR variables, and pass the pin numbers for every I2C call.

    Erlend
    21st century - when everything changes
    "Better with a DAT and a COG than with a CAT and a DOG"
  • Erlend wrote:
    ... If you think multiple busses will be the rule, I believe the most elegant way is to let pin numbers be VAR variables, and pass the pin numbers for every I2C call.
    ... or just pass the pin numbers to a start method that sets the VAR variables once for that object instance.

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Tracy AllenTracy Allen Posts: 5,933
    edited January 3 Vote Up0Vote Down
    Three main possibilities. All okay depending on the project plan.
    1) pass the pin numbers with each and every method call.
    -- i2c.method(sdapin,sclpin, da da da) for every i2c method
    -- pins default to inputs in all cogs so no initialization or start method is necessary for the pins per se.
    -- All sub objects have to know the pin numbers as constants or as passed from a parent object.
    -- Simple, but pin parameter(s) accompany every method call.
    2) Store the pin numbers in VAR space, passed in a Start or Init method.
    -- Every child object that uses the i2c object needs to be init'd
    -- Each initialization in the sub-objects creates its own copy of the pin variables
    -- Only copy of the object code in HUB, even with multiple instances of i2c buss
    -- Subsequent method calls do not need the two pin numbers as parameters in every method call.
    -- Bug potential in forgetting to init a sub-object's pins.
    3) Store the pin numbers in DAT space, passed in a Start or Init method.
    -- Only one object (usually the top one) needs to Init the pin numbers
    -- Child objects have to declare the i2c OBJ, but don't have to Init it, the DAT variables are global.
    -- Low bug potential, Init it and forget it.
    -- Method calls don't need the two pin numbers as parameters.
    -- Easy to cover with locks, important to avoid conflict if multiple cogs will share the same i2c buss
    -- Down side is multiple copies of the object code to support multiple instances of i2c buss.
  • @Tracy

    Nice summary. I have gotten there now, but the way OBJ, DAT, and VAR is handled in Spin was a big mystery to me. I prefer the DAT approach (as per my sig :) ).

    Erlend
    21st century - when everything changes
    "Better with a DAT and a COG than with a CAT and a DOG"
  • I do appreciate all of the suggestions and kind uploads which enabled me to get an array of 5 VL6180x sensors to successfully measure ranges ( I was not interested in the ALS - Ambient Light Sensor - functionality). For some odd reason I have not been able to successfully add a 6th, 7th, or 8th VL6180 x sensor to the array. I am using a Parallax Professional Development Board and have attached the files used to run the 5 - VL6180X sensors. I ran the diagnostics method that Erlend kindly provided in the i2cDriverX Spin software to test the i2C buses, and the only thing that I was able to determine which distinguishes the working 5 sensors from the non-working 6th sensor was that the diagnostic indicated that for the 6th sensor, "0 slaves found", whereas for each of the working sensors the diagnostics returned the result that "1 slave was found" . The "bus initialization" and status of the Scl and Sda indicated success for all 6 sensors. I have replaced the 6th sensor with others that successfully work and also I have swapped the software used with sensors 1-5 for the software used for the 6th sensor (with the small appropriate changes) and the same result occurs, which is that the 6th sensor produces no range readings. I also placed a LED indicator after the 6th cog method executed which indicates that the 6th cog did execute and that the method resumed control to the following program statements. I also changed pin designations and positions and wires on the development board for the 6th sensor in case there were defective connections, and again the 6th+ sensor would not operate. Any insight to what may be the problem will greatly be appreciated as I thought that after getting 2 sensors to run, that the rest would be a snap! In advance, thank you for your assistance. The main 'Master Control' program, as well as the 6 i2cDriverx objects and 6 Cog objects are attached.
  • The RC time constant might be too long to be acceptable for the chip itself or you need more or less time on that 6th device. The shift register approach that I mentioned is a sure and certain way of initializing these chips though.
  • In the future, Use the File> Archive in the Propeller Tool to save your project in one ZIP file.

    Infernal Machine
  • Sorry about that!
Sign In or Register to comment.