I2C devices - What's the preferred way to control multiple devices?

OLDmarty

Senior Member
Hi all,

I'm trying to get my head around how i address/control several i2c devices using a 40X2 and with hi2csetup & hi2cout commands.

I'm currently controlling some LED matrix drivers quite easily (Holtek 16k33 driver chip).

However, i want to place 2 (or more) of these LED matrices onto the ONE i2c bus.
From what i read in picaxe tutorials, It seems that i need to change the address numbers in my hi2csetup command each time i want to control a different device on the bus. Am i reading it correctly?

I thought the destination address would be sent in the hi2cout commands.

Can someone please share a preferred method to control multiple i2c devices on the ONE i2c bus?

Thanks in advance.
 

Aries

New Member
In essence you are right, although the X2 devices do allow the use of [newslave] in hi2cin and hi2cout to change addresses without using a new hi2csetup. [newslave] requires all the other parameters (addressing mode, speed) to be the same. If that is not the case, then you need to do a new hi2csetup each time. This is from a project which has three Picaxe 20X2 slaves controlled from a 28X2 master.

Subroutine CloseI2C does "hi2csetup off", checks that the I2C bus is not locked and unlocks it if necessary.

Incidentally, you can only have one I2C bus from one Picaxe, because the SDA and SCL pins are fixed.

Code:
' i2c addresses for slaves
symbol I2cClockSlave = %10111100
symbol I2cDisplaySlave = %10110000
symbol I2cActuatorSlave = %10110010
...
    gosub CloseI2C
    hi2csetup i2cmaster,I2cActuatorSlave,i2cslow,i2cbyte
    ptr = I2CScratchPad
    hi2cin I2CSensorLowBookEnd,_
                (@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,_
                 @ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc,@ptrinc)
    pause 100
...
    gosub CloseI2C
    hi2csetup i2cmaster,I2cDisplaySlave,i2cslow,i2cbyte
    ptr = i2cSwitchStatus
    hi2cin I2cSwitchStatus,(@ptrinc,@ptrinc,@ptrinc)    ' read in 3 bytes of status info
    pause 100
...
CloseI2C:
    hi2csetup off
    input I2C_SDA
    if I2C_SDApin = 0 then                ' I2C stuck
        sertxd(13,10,"I2C stuck")
        for b0 = 0 to 15
            if I2C_SDApin = 0 then
                low I2C_SCL                            ' pull low
                pause 10
                input I2C_SCL                        ' allow to go high
            endif
        next b0
    endif
    input I2C_SCL
    return
 

techElder

Well-known member
OLDmarty, it can be done, and Aries shows the traditional method of doing it. You just have to keep track of where you are sending data.

I used 20X2 in matrix LED displays, each with their own message, with a 28X2 driving them from the running program. Each display unit had buttons for feedback, too.

With the 20X2 as a slave, you are actually writing to its memory just as if it was an external memory device. The 20X2 program then has to know when that memory has changed and act accordingly.

PS. Using and adjusting your code, Aries, I think I've done your "stuck" check this way. Seemed quicker when quickly changing devices. I'm away from my code, so correct me, please.

Code:
CloseI2C:
    ' check b0 > 15 on exit for "I2C stuck" condition if needed
    hi2csetup off
    b0 = 0
    input I2C_SDA
    do while I2C_SDApin = 0                    ' I2C stuck
        low I2C_SCL                         ' pull low
        pause 10                            ' wait for it
        input I2C_SCL                       ' allow to go high
        inc b0                                ' increase the iteration
    loop until I2C_SDApin > 0 OR b0 > 15    ' exit the loop after "I2C not stuck" or 16 tries
    return
 

hippy

Technical Support
Staff member
There are two oprions. Explicitly define which device is to be communicated with using HI2CSETUP and then issue HI2COUT commands -
Code:
Do
  HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE
  HI2cOut $00, ( "A" )
  HI2cSetup I2C_MASTER, DEVICE_B, I2C_SLOW, I2C_BYTE
  HI2cOut $00, ( "B" )
Loop
Or, where the speed and address size are the same, issue a single HI2CSETUP and then specify which device a HI2COUT command is directed at -
Code:
HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE
Do
  HI2cOut [DEVICE_A], $00, ( "A" )
  HI2cOut [DEVICE_B], $00, ( "B" )
Loop
The choice is yours to make; whichever you prefer and suits your program best.
 

OLDmarty

Senior Member
Thanks guys for your replies, something to ponder for me.....

On a side note, here's some info for the Holtek 16K33 LED driver chip i'm playing with....(these are all over ebay these days)
Code:
The Holtek 16k33 led driver breakout uses 2 pins (I2C) to communicate.
There are multiple selectable I2C addresses. 
For devices with 2 Address pins (A0, A1) Select address(es): 0x70, 0x71, 0x72 or 0x73.
For devices with 3 Address pins (A0, A1, A2) Select address(es): 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76 or 0x77.
Here's my setup which is working fine. The default address of the 16k33 led driver board is set to "0" (no address jumpers fitted), or actually is address "70" according to the above text.
Code:
HI2CSETUP I2CMASTER,%11100000, I2CSLOW, I2CBYTE  ;address 0
I understand that the LSB bit is a reserved read/write bit in the HI2CSETUP command, so the next 3 bits (3,2,1) are for device addressing of these Holtek 16K33 chips.

I have also tried all these combinations (one at a time, of course) to prove the 16k33 led board still works under 8 different addresses as expected.
Code:
HI2CSETUP I2CMASTER,%11100010, I2CSLOW, I2CBYTE  ;address 1
HI2CSETUP I2CMASTER,%11100100, I2CSLOW, I2CBYTE  ;address 2
HI2CSETUP I2CMASTER,%11100110, I2CSLOW, I2CBYTE  ;address 3
HI2CSETUP I2CMASTER,%11101000, I2CSLOW, I2CBYTE  ;address 4
HI2CSETUP I2CMASTER,%11101010, I2CSLOW, I2CBYTE  ;address 5
HI2CSETUP I2CMASTER,%11101100, I2CSLOW, I2CBYTE  ;address 6
HI2CSETUP I2CMASTER,%11101110, I2CSLOW, I2CBYTE  ;address 7
What puzzles me, is the address being used, it's technically $E0 to $E7 if we read all 8 binary bits, but is the address being treated as a 7 bit address because of the reserved (ignored) LSB bit not being used for addressing?

I guess i'm on the right theory, but it can be a challenge for newbies wondering which addresses to use.
 

OLDmarty

Senior Member
There are two options. Explicitly define which device is to be communicated with using HI2CSETUP and then issue HI2COUT commands -
Code:
Do
  HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE
  HI2cOut $00, ( "A" )
  HI2cSetup I2C_MASTER, DEVICE_B, I2C_SLOW, I2C_BYTE
  HI2cOut $00, ( "B" )
Loop
Or, where the speed and address size are the same, issue a single HI2CSETUP and then specify which device a HI2COUT command is directed at -
Code:
HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE
Do
  HI2cOut [DEVICE_A], $00, ( "A" )
  HI2cOut [DEVICE_B], $00, ( "B" )
Loop
The choice is yours to make; whichever you prefer and suits your program best.

Thanks Hippy,

In your 2nd example, but i'm a bit lost, why did you write:
Code:
HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE
It seems to be focussed on DEVICE A, but the following lines seem to be catering for devices A and B etc.
Code:
Do
  HI2cOut [DEVICE_A], $00, ( "A" )
  HI2cOut [DEVICE_B], $00, ( "B" )
Loop
Can you explain what i should be putting in the "[DEVICE_A]" and "[DEVICE_B]" section of code? is this simply the addresses of my devices?

Thanks in advance.
 

inglewoodpete

Senior Member
while hippy is sleeping....

If you look at the command description for Hi2cOut, you will see that one of the syntax options allows you to do a one-off redirection of an i2c output to a specific address (shown as [newslave] in the command description). In your case, any of the 8-bit addresses you have listed in post #5 can be enclosed in square brackets within the Hi2cOut command. Try it, you'll like it! I've used this method to address up to 8 slaves on one bus.

Also, newslave can be expressed via a variable eg Hi2cOut [b10], $00, ("A"), if you want to use a lookup method to determine the i2c address pointer.
 

OLDmarty

Senior Member
while hippy is sleeping....

If you look at the command description for Hi2cOut, you will see that one of the syntax options allows you to do a one-off redirection of an i2c output to a specific address (shown as [newslave] in the command description). In your case, any of the 8-bit addresses you have listed in post #5 can be enclosed in square brackets within the Hi2cOut command. Try it, you'll like it! I've used this method to address up to 8 slaves on one bus.

Also, newslave can be expressed via a variable eg Hi2cOut [b10], $00, ("A"), if you want to use a lookup method to determine the i2c address pointer.

Thanks IP for clearing that up, i'll setup a few displays on different addresses and play with some code...

What should i be doing for the H2CSETUP line? "HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE"

Why is "DEVICE_A" shown in Hippy's example? or was it some cut'n'paste typo? ;-)

Thanks again.
 

inglewoodpete

Senior Member
Thanks IP for clearing that up, i'll setup a few displays on different addresses and play with some code...

What should i be doing for the H2CSETUP line? "HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE"

Why is "DEVICE_A" shown in Hippy's example? or was it some cut'n'paste typo? ;-)

Thanks again.
The Hi2cSetup command sets up the default parameters for the i2c. It is a mandatory command. It sets a default slave address to communicate with but you don't have to communicate with that device. "DEVICE_A" and "DEVICE_B" are symbols for different hypothetical devices that could be defined like the following code:
Rich (BB code):
   Symbol i2cAddrDS1307 = %11010000 '$D0: i2c address of the DS1307 RTC chip
   Symbol DEVICE_A      = %11100000 '$E0: OLDMarty's first slave device
   Symbol DEVICE_B      = %11100010 '$E2: OLDMarty's second slave device
   
   <other code>
   
   hi2cSetUp i2cMaster, i2cAddrDS1307, i2cslow, i2cbyte  ' Set Real Time Clock as the default slave 
      
   <other code>
   
   HI2cOut [DEVICE_B], $00, ( "B" ) 'Write something the OLDMarty's second slave device
 

OLDmarty

Senior Member
The Hi2cSetup command sets up the default parameters for the i2c. It is a mandatory command. It sets a default slave address to communicate with but you don't have to communicate with that device.

Thanks Pete, ok, I have set my config to address 0 as my default: "hi2cSetUp i2cMaster, %111000000, i2cslow, i2cbyte ".
( i mean address 0 of the display devices...i realise the overall address is "$E0" ).

I now have 2 devices addressed as '000' and '111' for proving to myself which data i'm sending to which display chip.
So far, i now have 2x display chips displaying unique data.... so far, so good. ;-)

Thanks again for the clarification, Greatly appreciated.
 

OLDmarty

Senior Member
OK, a quick follow-up...

For these tests, i only have 2 devices connected, one is set to address '0' and the other is set to address '7' (or $E0 and $EE respectively).

I noticed a slight issue with my device '7' not lighting up after a power cycle/reboot.
It would only come to life if i set its address to '0' and reboot. (device 7 has a BCD switch connect to its addressing pins for trials).

Then i realised all my init codes for *BOTH* the 16k33 chips needed to re repeated for each device address included.
(only my original device '0' was having its registers cleared,brightness set, and display turned on etc).

OK, as of now, i think it's all good & solid ;-)

My only concern now is that all the i2c pullups (except 2) on each device should probably be removed to avoid any bussing problems in future.
I imagine 8x devices with pullup resistors in parallel are going to result in a fairly small overall pullup value that might start glitching out data, or not work at all.

Has anyone else had to remove the surplus pullups before?
 

depeet

New Member
My only concern now is that all the i2c pullups (except 2) on each device should probably be removed to avoid any bussing problems in future.
I imagine 8x devices with pullup resistors in parallel are going to result in a fairly small overall pullup value that might start glitching out data, or not work at all.

Has anyone else had to remove the surplus pullups before?
I used to work with Arduino and did experience some communication problems when there were "too many" sensors and devices on the bus. Removing some resitors stabilised the communcation in my case. With my Arduino a total resistance between 4k7 and 10K turned out to be ideal. I don't know yet how Picaxe would react.
 

BESQUEUT

Senior Member
I used to work with Arduino and did experience some communication problems when there were "too many" sensors and devices on the bus. Removing some resitors stabilised the communcation in my case. With my Arduino a total resistance between 4k7 and 10K turned out to be ideal. I don't know yet how Picaxe would react.
This is conform to I2C BUS specification
 

hippy

Technical Support
Staff member
What should i be doing for the H2CSETUP line? "HI2cSetup I2C_MASTER, DEVICE_A, I2C_SLOW, I2C_BYTE"

Why is "DEVICE_A" shown in Hippy's example? or was it some cut'n'paste typo? ;-)
In the Hi2CSETUP command there needs to be some I2C Device Address specified, even if we are specifying the actual Device Address in the later HI2COUT or HI2CIN commands. It can in fact be any value which gets through the compiler without giving a Syntax Error but I arbitrarily chose DEVICE_A but could have chosen DEVICE_B.

As to DEVICE_A andr DEVICE_B; they were just illustrative. They could be defined in SYMBOL commands as per Inglewoodpete's Post #9 or replaced in the code by explicit $xx or %xxxxxxxx values.

On excessive pull-ups; they all parallel-up so the effective pull-up reduces as more boards are added to the bus if those boards are fitted with pull-ups so it may become necessary to remove those if using a number of boards.

There's no easy answer to the pull-up dilemma of where they should be. Generally there needs to be only one pull-up pair but they are often not fitted to the microcontroller board because that may interfere with the I/O usage when not used for I2C. That means boards will often include them to make them plug-and-play.

With one board it usually doesn't matter if there are pull-ups on the microcontroller or not, it merely reduces the effective pull-up values but they usually still work; it's also why they can be higher value pull-ups rather than lower, so when paralleled the combination, if on both, isn't immediately too low.

Add more modules and it keeps reducing as you noted. Most people only use one or two boards so not to much of a problem but more may mean removing pull-ups on additional boards.
 

inglewoodpete

Senior Member
Has anyone else had to remove the surplus pullups before?
Definitely yes. i2c devices are open drain (==open collector) and the low resistance to the +ve rail resulting from 7 pullup resistors in parallel is risking the bus driver FETs in any of the chips being overloaded, potentially causing them to fail.
 

grim_reaper

Senior Member
I've been struggling with I2C bus lockups for a few days and this thread solved that for me. However, I need to point out that the example (#3) given on the hi2csetup command page is incorrect!
I bookmarked the page so I could go back and use the example, only to find it didn't work - the logic is inverted - it's checking for SDA being high, not low!
I've obviously now bookmarked this thread instead, but I'm wondering if any less-experienced users are falling into the same pit of confusion when following the example?
(No other place to report this error as far as I can see)
 
Top