How do I communicate with a custom I2C built with a PCF8574?

This is where you talk about the NXJ software itself, installation issues, and programming talk.

Moderators: 99jonathan, roger, imaqine

How do I communicate with a custom I2C built with a PCF8574?

Postby enzomango » Wed Feb 27, 2008 3:04 am

I built a simple I2C sensor using a PCF8574 IC. The device is very similar to the examples found in the Extreme NXT book. The book's programming examples use NBC but I'm much more comfortable with Java. I haven't been able to locate any coding examples. Can anyone help guide me in the right direction?
enzomango
Novice
 
Posts: 30
Joined: Tue Feb 26, 2008 5:20 pm

Postby mdsmitty » Thu Feb 28, 2008 4:27 am

Look in the source folder in your lejos folder. there are several good examples for digital sensor classes. the classes for mindsensors IRlink and mindsensors rcx motor mux are good examples.
mdsmitty
Novice
 
Posts: 78
Joined: Tue Feb 27, 2007 3:07 am

Classes for PCF8574 and PCF8591

Postby enzomango » Wed Mar 05, 2008 2:28 am

Thanks. I was finally able to successfully develop and test icommand classes for the I2C PCF8574 I/O expander and the I2C PCF8591 A/D converter. They seem to work well. My biggest challenge was that neither device has internal registers and it appeared to me that both icommand and leJOS I2C methods expect to be passed internal register addresses. I found a simple workaround for icommand (see below) but I couldn't find a workaround for leJOS. It's very possible that leJOS can handle devices without internal registers and I just don't know how it's done. Please let me know if there's a way to use leJOS. I'd also like to know if I really had to go through the trouble of writing my own classes.

Code: Select all
******** PCF8574.java ********
import icommand.nxt.*;
import icommand.nxt.comm.*;

/**
 * This class is used to communicate with a PCF8574 two-wire I2C-bus
 * to 8-bit parallel bus I/O expander.
 * @author V. M.
 *
 */
public class PCF8574 extends I2CSensor {
   private static final NXTCommand nxtCommand = NXTCommand.getSingleton();
   
   /**
    * Returns the sensor port number.  Values range from 0-3 (maps to NXT
    * port numbers 1-4).
    */
   private byte portnum;
   
   /**
    * Returns the sensor address. 
    * Values range from 0x40 - 0x47 (or 0x70 - 0x77 when using a PCF8574A).
    */
   private byte address;
   
   /**
    * Initializes the PCF8574 sensor.
    * @param sensorPort NXT sensor port.
    * @param sensorAddress sensor address.  Values range from 0x40 - 0x47
    * (or 0x70 - 0x77 when using a PCF8574A).
    */
   public PCF8574(SensorPort sensorPort, byte sensorAddress) {
      super(sensorPort, NXTProtocol.LOWSPEED);
      portnum = (byte)sensorPort.getId();
      address = sensorAddress;
   }

   /**
    * Method for sending data to sensor.
    * @param value Data to send.
    * @return
    */
   public void sendData(byte value) {
      byte[] txData = {address, value};
      byte[] status; //response from sensor

      nxtCommand.LSWrite(portnum, txData, (byte)1); //send data
      do {
         status = nxtCommand.LSGetStatus(portnum); //check status
      } while (status[0] == ErrorMessages.PENDING_COMMUNICATION_TRANSACTION_IN_PROGRESS
               | status[0] == ErrorMessages.SPECIFIED_CHANNEL_CONNECTION_NOT_CONFIGURED_OR_BUSY);
   }
   
   /**
    * Method for retrieving the data from the sensor.
    * @return value of 8-bit parallel bus port.
    */
   public int getData() {
      byte[] txData = {address};
      byte[] status;

      nxtCommand.LSWrite(portnum, txData, (byte)1); //send request
      
      do {
         status = nxtCommand.LSGetStatus(portnum);
      } while (status[0] == ErrorMessages.PENDING_COMMUNICATION_TRANSACTION_IN_PROGRESS
               | status[0] == ErrorMessages.SPECIFIED_CHANNEL_CONNECTION_NOT_CONFIGURED_OR_BUSY);
      if(status[1] == 0) {
         System.out.println("No bytes to read. Returning -1.");
         return -1;
      }
            
      byte[] result = nxtCommand.LSRead(portnum);
      int x = (0xFF & result[0]);
      return x;
   }
   
   /**
    * Method for retrieving the status of one sensor port.
    * @param port the sensor port. Values range from 1-8.  This is the PCF8574, not the NXT port.
    * @return value of sensor port.  1 means port is high, 0 means port is low (pulled to ground).
    */
   public int getSensorPortStatus(int sensorPort) {
      int result = -1;
      int mask = -1;
      int data = getData(); //get status of all ports from sensor
      
      //create a mask to logically AND the data with...
      switch (sensorPort) {
           case 1:  mask = 1; break;
           case 2:  mask = 2; break;
           case 3:  mask = 4; break;
           case 4:  mask = 8; break;
           case 5:  mask = 16; break;
           case 6:  mask = 32; break;
           case 7:  mask = 64; break;
           case 8:  mask = 128; break;
      }
      result = (byte)mask & (byte)data;
      if ( result > 0 ) {
         return 1;
      }else {
         return 0;
      }
   }
}


******** PCF8591.java ********
import icommand.nxt.*;
import icommand.nxt.comm.*;

/**
 * This class is used to communicate with a PCF8591 8-bit A/D and D/A converter.
 * @author V. M.
 *
 */

public class PCF8591 extends I2CSensor {
   private static final NXTCommand nxtCommand = NXTCommand.getSingleton();
   
   /**
    * Returns the sensor port number.  Values range from 0-3 (maps to NXT
    * port numbers 1-4).
    */
   protected static byte portnum;
   
   /**
    * Returns the sensor address. 
    * Values range from 0x90 - 0x97.
    */
   protected static byte address;

   /**
    * Initializes A/D Converter.
    * @param sensorPort NXT sensor port.
    * @param sensorAddress sensor address.  Values range from 0x90 - 0x97.
    *
    */
   public PCF8591(SensorPort sensorPort, byte sensorAddress) {
      super(sensorPort, NXTProtocol.LOWSPEED);
      portnum = (byte)sensorPort.getId();
      address = sensorAddress;
   }
   
   /**
    * Method for retrieving the data from all four analog input channels.
    * Only works if auto-incremented channel selection is turned on (bit 3).
    * @param control sensor control byte.  0x04 (bit 3) turns on auto-incremented channel selection.
    * @return array containing value of each analog input channel (CH0-CH3).  Values range from 0 to 255;
    */
   public int[] getAllChannelValues(int control) {
      byte[] txData = {address, (byte)control};
      int[] intResult = {-1,-1,-1,-1};
      byte[] status;
      
      int autoset = (byte)0x04 & (byte)control;
      if ( autoset > 0 ) {
         nxtCommand.LSWrite(portnum, txData, (byte)5); //request five bytes because first byte doesn't count.
         
         do {
            status = nxtCommand.LSGetStatus(portnum);
         } while (status[0] == ErrorMessages.PENDING_COMMUNICATION_TRANSACTION_IN_PROGRESS
                  | status[0] == ErrorMessages.SPECIFIED_CHANNEL_CONNECTION_NOT_CONFIGURED_OR_BUSY);
         if(status[1] == 0) {
            System.out.println("No bytes to read. Returning 0.");
            return intResult;
         }
               
         byte[] result = nxtCommand.LSRead(portnum); //retrieve the five bytes
   
         for (int x=1;x<=4;x++) {
            intResult[x-1] = (0xFF & result[x]);
         }
         return intResult;
         
      }else {
         System.out.println("Error: Auto-incremented channel selection is not turned on (bit 3)!  Use getChannelValue() instead");
         return intResult;
      }
   }

   /**
    * Method for retrieving the value from one analog input channels.
    * @param control sensor control byte.  First two bits select channel.  0x00 channel 0, 0x01 channel 1, etc.
    * @return value of analog input channel (CH0-CH3).  Values range from 0 to 255;
    */
   public int getChannelValue(byte control) {
      byte[] txData = {address, (byte)(control)};
      int intResult = -1;
      byte[] status;
      
      nxtCommand.LSWrite(portnum, txData, (byte)2); //request two bytes because first doesn't count
      
      do {
         status = nxtCommand.LSGetStatus(portnum);
      } while (status[0] == ErrorMessages.PENDING_COMMUNICATION_TRANSACTION_IN_PROGRESS
               | status[0] == ErrorMessages.SPECIFIED_CHANNEL_CONNECTION_NOT_CONFIGURED_OR_BUSY);
      if(status[1] == 0) {
         System.out.println("No bytes to read. Returning -1.");
         return intResult;
      }
            
      byte[] result = nxtCommand.LSRead(portnum);
      return intResult = (0xFF & result[1]); //second byte
   }
}
enzomango
Novice
 
Posts: 30
Joined: Tue Feb 26, 2008 5:20 pm

Postby gloomyandy » Wed Mar 05, 2008 1:52 pm

Hi,
You will need to use the leJOS low level i2c access routines. So something like...

Code: Select all
   public int getData(int address, byte [] buf, int len) {   
      int ret = port.i2cStart(address, 0, 0, buf, len, 0);
      
      if (ret != 0) return ret;
      
      while (port.i2cBusy() != 0) {
         Thread.yield();
      }
      
      return 0;
   }

Should read len bytes into buf. Note that port (which is of type I2CPort, should have been initialized and enabled already (see the code in I2CSensor.java in the leJOS class files).

There are a couple of things that you also need to understand. The main one is I2C addressing. There is a lot of confusion about this. An i2c address is actually 7 bits in length. When it gets sent on the wire the address is shifted one bit left and the lsb is then used to indicate if this is a read or a write request. leJOS expects the unshifted 7 bit address and the read/write direction is specified separately (it is the last parameter in the Start method above). However some code (like your examples I think) treat the address as an 8 bit value with 0dd addresses being a "read" address and even addresses being a "write" address. So if the address you are using are in effect 8 bit (and I think they are looking at the sample code), you may need to convert them to 7 bits by shifting the address right by 1.

Hope this helps...

Andy
User avatar
gloomyandy
leJOS Team Member
 
Posts: 4004
Joined: Fri Sep 28, 2007 2:06 pm
Location: UK

Postby enzomango » Thu Mar 06, 2008 1:43 am

Thanks Andy!

I switched to the 7-bit address and developed two simple leJOS classes for the two different IC's and they both work perfectly now. But I'm still a little confused about the addressing because the icommand code is still using the 8-bit format. I tried to get the later working with a 7-bit address just for fun but I wasn't able. Regardless, I now have both IC's working with both API's. Who said you can't have your cake and eat it too?
enzomango
Novice
 
Posts: 30
Joined: Tue Feb 26, 2008 5:20 pm

Postby gloomyandy » Thu Mar 06, 2008 2:44 am

Hi!
Great news...

The addressing is a little confusing! ICommand uses the standard Lego firmware and so uses the low level Lego i2c commands. They basically just send the byte array on to the device. So when you place say 0x47 into byte 0 of the array, you are in effect doing the following...
Starting with the "real" 7 bit address 0x23 you shift it left 1 to get 0x46 then you want to do a read operation so you or in the "read" bit and get 0x47. In the leJOS firmware the address and read/write bits are passed in separately and the firmware puts them all together into the on the wire format, so you must pass 0x23 as the address and tell leJOS that you want to do a read. What ends up on the wire is the same in both cases...

Hope that makes some sort of sense....

Andy
User avatar
gloomyandy
leJOS Team Member
 
Posts: 4004
Joined: Fri Sep 28, 2007 2:06 pm
Location: UK


Return to NXJ Software

Who is online

Users browsing this forum: No registered users and 2 guests

more stuff