Initial contact with I2C

Hi,

I am trying to test a piece of h/w ( PMBus power supply ) which uses I2C for config and control. It's a retcovered item so I do not know whether it is fully operational. There is DC output and all looks fine but I can't get any comms working.

This may be just me messing up, since I've had little to do with I2C until this came up.

The manufacturer's doc says the default address is 3E and there is a read only, single byte status parameter in register D8.

I knocked up the following code to see whether I could establish comms and get this byte as a trivial test.

#define I2C_SLAVE_ADDR 0x3E

  void setup() {
	// initialize the serial communication:
	Serial.begin(115200);
        Serial.write("I2C-to-USB proto \r\n");
        Wire.begin();  // init TWI lib

        Wire.endTransmission();  // tidy up ...
	byte status=0;

  
       Serial.println("send request - get status byte");
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       Wire.write((byte)0xD8); // set status register address
       Wire.endTransmission();  
       delay(100);
       Wire.requestFrom(I2C_SLAVE_ADDR,1); //read case status byte
       if ( Wire.available() >=1) {
         status=Wire.read() ;
         Serial.println(status,HEX);
       }
       else {
         Serial.println("No data on bus\r\n");
      }
        Wire.endTransmission();
}

When I reset I just get back the message indicating there is nothing available from the device.

"No data on bus"

The Wire lib seems to mess about with the usual I2C read/write convention and AFAICT it requires me to do a false write to set the address I want to read from and fiddles with the read/write bit itself. It appears that the first call to Wire.Write() does not actually write anything :?

Neither beginTransmission() nor Write() provide a return value , so I don't know if that was successful. ;(

Can someone at least confirm I'm doing this correctly? I've seem several code examples which are all a bit different.

TIA.

have you tried the I2C scanner?
https://playground.arduino.cc/Main/I2cScanner

also use an oscilloscope to look at the clock and data lines to see if there is a response

You need to run the I2C scanner as @horace wrote.

Is it a Artesyn embedded power supply ? (just a guess after a Google search).
I think it uses a pic controller in Slave mode.
The PMBus is not a normal I2C bus, perhaps there are libraries for it.
Can you measure the SDA and SCL idle voltages ? If those are 3V, then perhaps you need a 3V I2C bus.

yes, good guess, it's an iMP series PSU. The doc is fairly good. 5V internal supply in I2C.

I tried looking at voltages with a DVM and could not even find a pin connected to ground which seemed very odd. The output on their J2 uses 5V on pin 10 ; gnd on 9; and signals on 4 adn 5, opposite to A4,A5 used by Wire lib.

I took the casing off and nothing seemed obviously damaged or missing, so I'm a little confused that nothing seems connected.

The unit does provide the output voltage but seems to shut down easily, so I suspect the current limit is low on power up. Once it goes into shut down I need to power cycle it unless I can suss the serial comms.

Thanks, I2cScanner looks useful, if I can find something to look at !

if you do a web search for pmbus vs i2c you will see plenty of discussions

you may have to bitbash the communications !

The I2C signals requires that the GNDs are connected. You need three wires.
For the i2c scanner, you have to open the serial monitor. You can keep the i2c scanner running, while trying to connect SDA and SCL. The SDA and SCL should be idle high (near 5V for a 5V I2C bus).

The Uno gnd is connected to the PSU chasis gnd via the mains earth. I did not want to create an earth loop. It may be that the I2C gnd is floating or that the whole thing has been blown out my mis-use or a storm. :frowning:

Ah, that's more like it!

Scanning...
I2C device found at address 0x1F !
I2C device found at address 0x57 !
done

Not what's in the doc but at least I'm off the ground. Many thanks for the scanner tip.

The address the scanner found at 0x1F is probably the one the manufacturer referred to as 0x3E. When sending the address byte to the I2C hardware, the 7-bit address is in the upper 7 bits and the low order bit is a read/write bit. Some manufacturers specify the full 8 bits for the address with the low order bit set to zero. In the Arduino, the address is specified by removing the low order bit and using only the 7-bits of the address itself.
0x3E = B00111110. Shifting it down one bit is B00011111 which is 0x1F.
This should work:

#define I2C_SLAVE_ADDR 0x1F

Pete

Of course ! Thanks for pointing out the equivalence. I'm still getting used to the illogical way TWI ( three wire interface ! ) has to make everything more confusing with fake writes and address mangling.

I was using 0x1F since that is what the scanner matched. However, it seems all reads are returning zero. I hope the PIC has not got a blow out SDA :?

The PMBus might not work with the normal Wire.requestFrom(). I have been reading about this, but I still don't understand it. Can someone explain how to do a basic register read with the PMBus ?
I think that the SDA is probably still okay, it is the PMBus protocol.

Writing and reading to registers require a specific way to do that, it is not like the normal I2C sensors (I think). Your code is perhaps for normal I2C sensors. I'm not sure if that will work for the PMBus.

I think it is like this:
Writing data:

Wire.beginTransmission(
Wire.write( command
Wire.write( length
Wire.write( data 
Wire.write( more data
Wire.endTransmission

Reading data:

Wire.beginTransmission(
Wire.write( command
Wire.endTransmission(false);   // repeated start !
Wire.requestFrom(    // how many bytes to request ?

The first byte of the received data will tell how many bytes will follow.

Do you have a logic analyzer ? Many of them can decode I2C signals and show the data bytes.
Something good: https://www.saleae.com/
Something cheap: Supported hardware - sigrok.

Find one of those cheap modules on AliExpress and use sigrok + PulseView. Then you have a logic analyzer for less than 10 dollars.

Thanks, some of the PMBus commands do have multiple bytes starting with a count byte. The single byte reads are just that : a single byte, no length.

the technical manual says:

98h PMBUS_REVISION "Read Byte" 1

SMBus Transaction Type = "Read Byte" ; Number of data bytes =1

I'm getting a zero which does not seem credible.

Yes a signal analyser would be nice, but I don't want to wait 2 weeks and don't have one here.

The first byte of the received data will tell how many bytes will follow.

Are you meaning that is what the Wire lib will do or what PMBus will send?

OK, I have got a finite status byte back. I was just not using the lib calls correctly.

       Serial.println("send request - get case status byte");
       Wire.beginTransmission(I2C_SLAVE_ADDR);
       res = Wire.endTransmission();  
       if (res != 0 ) { 
         Serial.println("No device responding"); 
       } 
       else {
         Wire.beginTransmission(I2C_SLAVE_ADDR);
         Wire.write((byte)0xD8); // set status register address
         Wire.requestFrom(I2C_SLAVE_ADDR,1); //read case status byte
         Wire.endTransmission();
         if ( Wire.available() >=1) {
           status=Wire.read() ;
           Serial.print("status = "); Serial.println(status,HEX);
         }
         else {
           Serial.println("No data on bus\r\n");
         }

send request - get case status byte
status = 35
PMBUS_REVISION = 0

Still a prob on the follow up but at least it’s working.

Thanks for you helpful suggestions.

I'm glad you are making progress.

Do you use pullup resistors ? Or are they already in the device ?
A start for I2C is this: Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino

The Wire library is still used in the wrong way by 20% of the experienced and good Arduino programmers. I did not comment on your code before, because it is not a full sketch.

The Wire library uses buffers. I think it has three buffers. You can either write (beginTransmission - write - endTransmission) or read (requestFrom).

The Wire.beginTransmission() sets some variables.
The Wire.write() writes bytes to a buffer in the Wire library.
The Wire.endTransmission() does the start, sending data from the buffer, and the stop.

The Wire.requestFrom() does the start, receiving the data into a buffer, and the stop. After that the data is available from the buffer in the Wire library.

Don't mix those two things together.

In setup():

Wire.endTransmission(); // tidy up ...

Please remove that line. It might cause a unknown transaction on the bus. It is not "tidy up", it is "mess up the I2C bus so everything will be irrelevant from now on" :smiling_imp:

The cast to a byte is not needed:

Wire.write((byte)0xD8);

You can use 0xD8 as it is. It is not really an optimization. Someone started to do that, and others thought that it was smart, but it is not.

Remove the Wire.endTransmission() after a Wire.requestFrom() :angry: The Wire.endTransmission() should only be used when writing bytes and should only be used together with Wire.beginTransmission(). For the AVR Wire library, a Wire.endTransmission() on its own will create an extra I2C address on the bus, and the Slave will acknowledge that. It could confuse the Slave, because the Slave is not expecting that at that moment.

There is some discussion about this:

if ( Wire.available() >=1)

When just 1 bytes is requested then I prefer to test if 1 byte is received "if (Wire.available() == 1)". Others still want to test if perhaps more bytes are received with ">=". It is however impossible to receive more than requested. If someone sends you 20 dollars, then the transaction was a success if you received 20 dollars. It is as simple as that.

Yes, there are pull-ups in the PSU.

The Wire library is still used in the wrong way by 20% of the experienced and good Arduino programmers.

Probably because it's so poorly documented. Your last comment is more use than the language reference ! It seems one needs to spend a day studying the source code to find out how to use the library, at which point it may be quicker to write your own code.

In setup():

Please refer to the code in my previous post. That line is gone and the redundant ends have been removed. I had been basing what I did on code sketches posted by others. It seems like the blind leading the blind.

You can use 0xD8 as it is. It is not really an optimization. Someone started to do that, and others thought that it was smart, but it is not.

I did not think it was an optimisation but I want to be sure it is doing what I intend. With fuzzy typing and multiple overloaded functions I'm never sure what is being passed.

It is however impossible to receive more than requested.

I originally used if ( Wire.available() ) but since the lib requires me to use a write when I want to read, I've learnt not to expect what would seem to be obvious. I put that in in case it was doing something unexpected. Part of debugging is checking things are doing what you intend and expect. Thanks for the comment, now I know it's not needed.

The Wire.endTransmission() should only be used when writing bytes and should only be used together with Wire.beginTransmission().

It probably would have taken me less time to write it myself writing directly to the atmel ports. This is pretty typical of my experience with Arduino libraries.

So do I need an endTransmission() when I do the false write before a read? What is the proper sequence to just read a single byte via I2C ? That is what I have been trying to discover for the last two days !!

Thanks again for your help.

When you send an 'int' with Wire.write(), only a byte is used. The Wire library transfers bytes, nothing else.
Sending all the 16 bits of an 'int' has to be done at a higher level, because the Wire library does not do that for you.
Therefor, fuzzy typing or not, the Wire.write() will only write a byte (or multiple bytes with other parameters).

Over a number of years, some bugs have been removed from the Wire library. The Wire library for AVR microcontrollers is now reliable. It is almost impossible to write your own I2C library. Many have tried that and many have failed. About 99.9% has failed.

The Arduino is for fast prototyping and to start with programming. It is designed by students and teachers, not by software engineers with many years experience with embedded systems. Keep that in mind.

I do not know the proper sequence to read a single byte with the PMBus protocol !
I'm very sorry, but this is still foggy for me. I tried to read about the PMBus, but the information is not always clear.
A few posts back I wrote that the sequence differs from normal sensors, but then I look at this: https://github.com/systronix/Systronix_LCM300 and it looks partially like the protocol for a normal sensor and partially what I described a few posts earlier.

I don't have a PMBus device, so I can't test a few things. You are pretty much on your own :frowning:
You could try that library on Github. There is a issue which makes it confusing for me even more.

I don't understand your question about a false write. There is no false write. For a repeated start, use the parameter 'false' with Wire.endTransmission(false), followed by a Wire.requestFrom().
(but never use the Wire.endTransmission() on its own, always with beginTransmission and almost always with write).

I don't understand your question about a false write.

PMBus is a superset of I2C. The underlying protocol should work the same. To read a device register you send a byte with the last bit set. To write you send the same byte with LSB zero. The device will either respond ( read ) or wait for the data ( write ).

why do I need to call Wire.Write() if I want to read ?

The Arduino is for fast prototyping

Which is why I thought it would be a quick way to test whether this device works. I was mistaken.

This explains the device. There are some links in the top of this doc. to the basic standards.

a PC based USB-I2C device may be useful?
https://www.robot-electronics.co.uk/htm/usb_i2c_tech.htm

LOL, that board was the first thing I looked at but then I thought , hey, I have a spare Arduino in the box. I'm sure I can find a sketch that does that, I'll just flash it and test the PSU. No need to buy something and wait in for a delivery service.

I should have taken more notice of my spelling checker: it underlines Arduino and suggests "arduous". Could not be more right.

About the Wire library: It uses the 7-bits I2C address shifted one bit to the right.

When writing with the function combination of "beginTransmission - write - endTransmission" the Wire library shifts the address to the left and adds a write bit ('0').

When reading with the function "requestFrom" the Wire library shifts the address to the left and adds a read bit ('1').

Thanks, I realise that. I’m using 0x1F, the number which Nick Gammon’s scanner finds ( also using wire lib ).
Here’s the whole sketch for reference.

#include <Wire.h>

#define I2C_SLAVE_ADDR 0x57  // 0x1F << 1 = 0x3E

void setup() {
 Serial.begin(115200);
 Serial.write("Arduino I2C v0\r\n");
 
 Wire.begin();  // init TWI lib
 init_I2C();
}


void init_I2C() {
  
  byte status=0;
  byte res=0;
  Wire.beginTransmission(I2C_SLAVE_ADDR);
  if ( Wire.endTransmission() !=0 ) {
     Serial.println("No slave on bus\r\n Halting.");
     while (1) ;
  }
   
     Serial.println("send request - get case status byte");
      Wire.beginTransmission(I2C_SLAVE_ADDR);
      res = Wire.endTransmission(); 
      if (res != 0 ) {
        Serial.println("No device responding");
      }
      else {
        Wire.beginTransmission(I2C_SLAVE_ADDR);
        Wire.write((byte)0xD8); // set status register address
        Wire.requestFrom(I2C_SLAVE_ADDR,1); //read case status byte
        res=Wire.endTransmission();
        if ((res != 0 ) || ( Wire.available() == 0 )) {
           Serial.println("No data on bus\r\n");
        } else {
          status=Wire.read() ;
          Serial.print("status = "); Serial.println(status,HEX);
        }  
      } // endif not responding


      // next cmd 
      Serial.println("send request 2 - get case status byte");
      Wire.beginTransmission(I2C_SLAVE_ADDR);
      res = Wire.endTransmission(); 
      if (res != 0 ) {
        Serial.println("No device responding");
      }
      else {
        
        Wire.beginTransmission(I2C_SLAVE_ADDR);
        Wire.write((byte)0xD8); // set status register address
        Wire.requestFrom(I2C_SLAVE_ADDR,1); //read case status byte
        Wire.endTransmission();
        if ((res != 0 ) || ( Wire.available() == 0 )) {
          Serial.println("No data on bus\r\n");
        }
        else {
          status=Wire.read() ;
          Serial.print("status = "); Serial.println(status,HEX);
        }  

             
      } // endif not responding 2

} // end init_I2C()


void loop() {
while (1) ;
}

This gets a finite value of status byte the first time but gets zero if I repeat the operation.

Arduino I2C v0
send request - get case status byte
status = A5
send request 2 - get case status byte
status = 0

I have no idea whether the first value is legit or bus garbage but clearly the second is garbage.