CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

i2c routines for dallas ds1624, philips pcf8574, and 24cXX
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> Code Library
View previous topic :: View next topic  
Author Message
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

i2c routines for dallas ds1624, philips pcf8574, and 24cXX
PostPosted: Fri Jun 04, 2004 11:25 pm     Reply with quote

Code:

// lib-i2c.h   i2c library routines

// routines for:
//   dallas ds1624 eeprom+thermometer
//   philips pcf8574 (and pcf8574a) 8port i/o expander
//   generic 24cXX eeproms with 16bit address support
//

// (C) copyright 2003 j.d.sandoz / jds-pic !at! losdos.dyndns.org 

// released under the GNU GENERAL PUBLIC LICENSE (GPL)
// refer to http://www.gnu.org/licenses/gpl.txt

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

// note: CCS's C compiler doesn't accept compile-time directives
//       to change the i2c pin definitions.  thus, if you have
//       multiple i2c buses in your application, every function
//       needs to be replicated for each of the i2c bus definitions,
//       with an appropriate use_i2c() directive preceeding it.


#define I2CWRITE     0b00000000
#define I2CREAD      0b00000001

// this is a handy function for taking the i2c physical address
// and shifting the bits left so that the address can simply be
// OR'd with an i2c write command...

int i2c_addr_mask(int device_addr) {
   /* xxxxxIJK --> 00000IJK, then 00000IJK --> 0000IJK0 */
   return((device_addr & 0x07)<<1);
}


// ----------------------------------------------------------------------------

// routines for the dallas DS1624 i2c-based (temp + 256b eeprom) ic
//   essentials:
//   ds1624_init();   initializes device for continous temperature readings
//   ds1624_read_temperature_c();    reads temperature in deg C
//   ds1624_eeprom_read();           reads a byte from address       
//   ds1624_eeprom_write();          writes a byte into address     
//   ds1624_eeprom_clear();          clears the contents of the eeprom

#define DS1624_ID                      0b10010000
#define DS1624_CMD_ACCESSCONFIG        0xAC
#define DS1624_CMD_INITIATECONVERT     0xEE
#define DS1624_CMD_ACCESSTEMPERATURE   0xAA
#define DS1624_CMD_ACCESSMEMORY        0x17

void i2c_ds1624_init(int device_addr) {
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(DS1624_ID | addr_mask | I2CWRITE); /* mode is write */
   i2c_write(DS1624_CMD_ACCESSCONFIG);          /* send access config command */
   i2c_write(0x00);       /* set up for continuous temp conversions */
   i2c_stop();
   delay_ms(20);          /* wait for the data to be written */
   i2c_start();
   i2c_write(DS1624_ID | addr_mask | I2CWRITE);   /* mode is write */
   i2c_write(DS1624_CMD_INITIATECONVERT);   /* initiate temperature conversions */
   i2c_stop();
}

int i2c_ds1624_read_temp_c(int device_addr) {
   int datah, datal;
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(DS1624_ID | addr_mask | I2CWRITE); /* mode is write */
   i2c_write(DS1624_CMD_ACCESSTEMPERATURE); /* send access temperature command */
   i2c_start();
   i2c_write(DS1624_ID | addr_mask | I2CREAD);  /* mode is read */
   datah=i2c_read();                  /* msb */
   datal=i2c_read(0);                 /* lsb (=0.5 deg C) & No Ack */
   i2c_stop();
   if (BIT_TEST(datah,7)) /* defeat the two's complement data output; */
      return(0);          /* this means NO negative temps are returned */
   else                   /* if datal is > 0.5C, round up the value */
      return( ((datal & 0b10000000) ? datah+1 : datah) ); /* returns 0->125 deg C */
}

int i2c_ds1624_eeprom_read(int device_addr, int address) {
   int data;
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(DS1624_ID | addr_mask | I2CWRITE); /* mode is write */
   i2c_write(DS1624_CMD_ACCESSMEMORY);  /* access memory command */
   i2c_write(address);                  /* address of memory to read from */
   i2c_start();                         /* repeated start */
   i2c_write(DS1624_ID | addr_mask | I2CREAD); /* mode is read */
   data=i2c_read(0);                    /* read the data, No Ack the read */
   i2c_stop();
   return(data);
}

int i2c_ds1624_eeprom_write(int device_addr, int address, int data) {
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(DS1624_ID | addr_mask | I2CWRITE); /* mode is write */
   i2c_write(DS1624_CMD_ACCESSMEMORY);  /* access memory command */
   i2c_write(address);                  /* address of memory to write to */
   i2c_write(data);                     /* data to be written */
   i2c_stop();
   delay_ms(20); /* worst case write time for the eeprom */
   return (i2c_ds1624_eeprom_read(device_addr, address));
}

long i2c_ds1624_eeprom_clear(int device_addr, int fill_byte) {
   long i, memsum=0;
   for (i=0;i<=255;i++) {    /* mind the (int) rollover! */
      i2c_ds1624_eeprom_write(device_addr,(int)i,fill_byte);
      memsum+=i2c_ds1624_eeprom_read(device_addr,(int)i);
      restart_wdt();
      }
   return(memsum);
}


// ----------------------------------------------------------------------------

// routines for the philips PCF8574/(A) i2c based 8 I/O chip
//   i2c_pcf8574_get_io();           reads state from 1 or all 8 inputs
//   i2c_pcf8574_set_io();           set the state all 8 outputs
//   i2c_device_exists();    test for presence of device


#define PCF8574_ID   0b01000000
#define PCF8574A_ID  0b01110000

int i2c_pcf8574_get_io(int device_type, int device_addr,int port_id) {
                  /*  0 <= port_id <= 8, or 8 for all  */
   int io_field;
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(device_type | addr_mask | I2CREAD);
   io_field=i2c_read();    /*  this byte contains the prior conversion result. */
   io_field=i2c_read(0);   /*  returned data is big-endian (msbit first)  */
   i2c_stop();
   if (port_id >= 8)
      return(io_field);
   if (BIT_TEST(io_field,port_id))
      return(1);
   else
      return(0);
}

// note: in order to implement "per bit" port control, it will be necessary
//       to first read all bits, and then OR that value with the bit you are
//       trying to toggle.  otherwise, you'll just end up overwriting adjacent
//       io bits and thus inadvertently changing other io lines. 
int i2c_pcf8574_set_io(int device_type, int device_addr, int io_field) {
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(device_type | addr_mask | I2CWRITE);
   i2c_write(io_field);
   i2c_write(io_field);
   i2c_stop();
   return(io_field);
}

// note: due to a production screw-up, some PCB assy's were delivered with
//       the "A" version of the pcf8574 device; while the responsible parties
//       were in fact tortured by being forced to consume large quantites
//       of a popular laxative, it was nevertheless necessary to implement
//       a scheme by which the FW could detect which flavor pcf8574 was on
//       the board.  hence the following function... call it using either
//       PCF8574_ID or PCF8574A_ID and test the return value.  this function
//       is actually not pcf8574-specific; you can use it to test for the
//       presence of any type of i2c device at any i2c physical address.

short int i2c_device_exists(int device_type, int device_addr) {
   short int result=FALSE;
   int addr_mask;
   int testbyte;
   addr_mask=i2c_addr_mask(device_addr);
   testbyte=(device_type | addr_mask | I2CWRITE); // mode is write
   i2c_start();
   if (i2c_write(testbyte))  // if the ACK error bit is set, target is absent
        result=FALSE;
     else
        result=TRUE;
   i2c_stop();
   return(result);
}

// ----------------------------------------------------------------------------

// routines for 24cXX eeproms with 16bit address support (e.g. 24C64)

#define EEPROM_24CXX 0b10100000

int i2c_24cXX_eeprom_read(int device_addr, int addrlen, int16 memory_addr) {
   int data;
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(EEPROM_24CXX | addr_mask | I2CWRITE); // mode is write
      if (addrlen==ADDR_LEN_16) {
      i2c_write(make8(memory_addr,1));
      i2c_write(make8(memory_addr,0));
      }
   else
      i2c_write((int)memory_addr);     // address of memory to read from
   i2c_start();                        // repeated start
   i2c_write(EEPROM_24CXX | addr_mask | I2CREAD); // mode is read
   data=i2c_read(0);                   // read the data, No Ack the read
   i2c_stop();
   return(data);
}


int i2c_24cXX_eeprom_write(int device_addr, int addrlen, int16 memory_addr, int memory_val) {
   int data;
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(EEPROM_24CXX | addr_mask | I2CWRITE); // mode is write
   if (addrlen==ADDR_LEN_16) {
      i2c_write(make8(memory_addr,1));
      i2c_write(make8(memory_addr,0));
      }
   else
      i2c_write((int)memory_addr);
   i2c_write(memory_val);
   i2c_stop();
   if (addrlen==ADDR_LEN_16) // only for rev B process parts!  see ST data sheet,
      delay_ms(5);           // http://us.st.com/stonline/books/pdf/docs/4578.pdf
   else
      delay_ms(10);  // enforced delay for writing
   return(i2c_24cXX_eeprom_read(addrlen,device_addr,memory_addr));
}


jahan



Joined: 04 Apr 2005
Posts: 63

View user's profile Send private message Send e-mail

device_addr
PostPosted: Tue Jan 17, 2006 9:57 am     Reply with quote

Hi,
in you code when you use "device_addr" as parameter to the functions, what are these values? 000, 001, 010, etc or 1,2,3?
would you please post a sample code to use your subroutines?

Thankx
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

Re: device_addr
PostPosted: Tue Jan 17, 2006 1:12 pm     Reply with quote

jahan wrote:
Hi,
in you code when you use "device_addr" as parameter to the functions, what are these values? 000, 001, 010, etc or 1,2,3?

typical i2c devices use three address pins (A0, A1, A2) to set the i2c slave address. this allows you to have multiple devices of the same type on the same i2c bus. note that some i2c devices only have A0 or A0/A1 pins, so fewer of them can be accomodated on one i2c bus.

as for how these values are represented, you can do it any way you want. note that a prefix of "0" represents an octal representation, therefore do not use "001" in your code unless you specifically know you are playing with octal values.

in general, since the the HW is unchanging, i would set the i2c device addresses in define statements, as such:
Code:

#define DS1624_I2C_ADDRESS       0 /* temperature sensor + 256byte eeprom on SCTL */
#define PCF8574_I2C_ADDR_POWER   1 /* 8 port i/o expander on SCTL, provides power info */
#define PCF8574_I2C_ADDR_SCTLID  2 /* 8 port i/o expander on SCTL, provides rotary switch info */
#define PCF8574_I2C_ADDR_INV0    3 /* 8 port i/o expander on SCTL, provides inventory slots 0...7 */
#define PCF8574_I2C_ADDR_INV1    4 /* 8 port i/o expander on SCTL, provides inventory slots 8...15 */


these correspond to the way the hardware pins (A0/A1/A2) are pulled in a given design.

also, each specific type of i2c device also has a 4 bit "family code" which defines what it does. eeproms have a unique code, temperature sensors have a unique code, and so on. these also vary from vendor to vendor. examples:
Code:

#define DS1624_ID                      0b10010000
#define PCF8574_ID   0b01000000
#define PCF8574A_ID  0b01110000


jahan wrote:

would you please post a sample code to use your subroutines?


as you can see from the i2c spec, the first four bits of an i2c command is the device type, then 3 bits of device address, and finally a read/write bit. above, i have a little function (i2c_addr_mask(int device_addr))which gets the 3 device address bits in the right place.

so you see the definition of
Code:
int i2c_pcf8574_get_io(int device_type, int device_addr,int port_id)
{                 /*  0 <= port_id <= 8, or 8 for all  */


then, calling the i2c library function in your main like this:
Code:
switch_val=i2c_pcf8574_get_io(PCF8574A_ID,PCF8574_I2C_ADR_SCTLID,IOALL);


results in the following after i2c start():
Code:

<snip>
i2c_write(device_type | addr_mask | I2CREAD);
<snip>


as you can see, the write consists of the device type (passed parameter 1), the address_mask (passed parameter 2 + a little manipulation per the function italized ablve), and finally the read bit is set. this is the basic construct of all of the i2c library functions.

jds-pic
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

PostPosted: Tue Jan 17, 2006 8:56 pm     Reply with quote

other mainline examples...

simple:
Code:

if (i2c_device_exists(EEPROM_24CXX_ID, 2) {
  do_some_operation_on_the_eeprom_at_addr_2();
  }


again simple:
Code:
i2c_ds1624_init(DS1624_I2C_ADDRESS);
temperature=i2c_ds1624_read_temp_c(DS1624_I2C_ADDRESS);


more complex:
Code:
int read_eeprom_into_buffer(int eeprom_address, int start, int end) {
   int index=0;
   for (; start<=end; start++)
      buffer[index++]=i2c_24cXX_eeprom_read(eeprom_address,start);
   buffer[index]=NULL;
   return(index);  // should be ( (end-start)+1 )
   }


again more complex:
Code:
         case HOST_CMD_CLEAR_HISTORY: {
            printf(serial_putc,"200: history...");
            for (i=0; i<LOCAL_I2C_MEM_SIZE; i++)
               if (i2c_24cXX_eeprom_read(ADDR_LEN_16, LOCAL_I2C_MEM_ADDR, (int16)i))
                  i2c_24cXX_eeprom_write(ADDR_LEN_16, LOCAL_I2C_MEM_ADDR, (int16)i, 0x00);
            printf(serial_putc,"cleared.\r\n");
The Puma



Joined: 23 Apr 2004
Posts: 227
Location: The Netherlands

View user's profile Send private message

PostPosted: Sat Mar 18, 2006 1:20 pm     Reply with quote

I can't get it ot work.

How can i read the state off pin P0?
Neckruin



Joined: 17 Jan 2006
Posts: 66

View user's profile Send private message

PostPosted: Tue Oct 17, 2006 1:44 am     Reply with quote

Hello The [spam],
I'm just going to use this driver in an application I'm working in. I suposse you finally solved your problem. Could you explain me the problem you had and how you solved it in case I find the same as well??

Thanks,

Juanma
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

PostPosted: Wed Oct 18, 2006 12:38 pm     Reply with quote

Neckruin wrote:
Hello The [spam],
Could you explain me the problem you had and how you solved it in case I find the same as well??


if you have a problem, just post the following info and we can find it and fix it in hours.

code?
schematic?
board matches schematic?
parts match BOM? (check i2c pullups)

behaviour before problem started?
changes that were made?
behaviour after problem was noticed?

what troubleshooting have you tried?
what were the results?


jds-pic
Neckruin



Joined: 17 Jan 2006
Posts: 66

View user's profile Send private message

PostPosted: Thu Nov 16, 2006 7:01 am     Reply with quote

Hello,
Does the Philips PCF8574 driver work for the Texas Instruments PCF8574???
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

PostPosted: Thu Nov 16, 2006 8:21 am     Reply with quote

Neckruin wrote:
Hello,
Does the Philips PCF8574 driver work for the Texas Instruments PCF8574???


it should, as the TI part is a direct second source replacement.

jds-pic
Neckruin



Joined: 17 Jan 2006
Posts: 66

View user's profile Send private message

PostPosted: Thu Nov 16, 2006 9:25 am     Reply with quote

Ok, thank you very much.

Just out of curiosity... what is exactly a "second source replacement"?
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

PostPosted: Thu Nov 16, 2006 12:34 pm     Reply with quote

Neckruin wrote:
Ok, thank you very much.

Just out of curiosity... what is exactly a "second source replacement"?


if the Philips factory burns down, the TI part is a direct form/fit/function equivalent.

jds-pic
Neckruin



Joined: 17 Jan 2006
Posts: 66

View user's profile Send private message

PostPosted: Fri Nov 17, 2006 2:37 am     Reply with quote

Laughing
Well, that means that the Philips stuff don't need to worry at all in case of fire.
- What's that??
- Just fire, forget it

Laughing
Dimlow



Joined: 24 Nov 2007
Posts: 9

View user's profile Send private message

PostPosted: Tue Jan 01, 2008 7:13 pm     Reply with quote

In this section of code, can you explain why you read and write the data twice


i understand the io_field=i2c_read(0); but why the io_field=i2c_read(); ???

Code:
int i2c_pcf8574_get_io(int device_type, int device_addr,int port_id) {
                  /*  0 <= port_id <= 8, or 8 for all  */
   int io_field;
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(device_type | addr_mask | I2CREAD);
   io_field=i2c_read();    /*  this byte contains the prior conversion result. */
   io_field=i2c_read(0);   /*  returned data is big-endian (msbit first)  */
   i2c_stop();
   if (port_id >= 8)
      return(io_field);
   if (BIT_TEST(io_field,port_id))
      return(1);
   else
      return(0);
}


and again here.. i2c_write(io_field);
Code:
int i2c_pcf8574_set_io(int device_type, int device_addr, int io_field) {
   int addr_mask;
   addr_mask=i2c_addr_mask(device_addr);
   i2c_start();
   i2c_write(device_type | addr_mask | I2CWRITE);
   i2c_write(io_field);
   i2c_write(io_field);
   i2c_stop();
   return(io_field);
}


according to the data sheet i have, data is valid after the acknowledge of the write or the read, two writes are not needed, the same goes for reading the port. But i may have interpreted the data sheet incorrectly ?
Regards
Gary
jds-pic



Joined: 17 Sep 2003
Posts: 205

View user's profile Send private message

PostPosted: Tue Jan 01, 2008 8:31 pm     Reply with quote

Dimlow wrote:

according to the data sheet i have, data is valid after the acknowledge of the write or the read, two writes are not needed, the same goes for reading the port. But i may have interpreted the data sheet incorrectly ?


gary,

re:
http://www.datasheetcatalog.com/datasheets_pdf/P/C/F/8/PCF8574.shtml

the Philips PCF8574 and TI PCF8574 datasheets seem to disagree on whether the data is valid after the first read. i could never get a straight answer of Philips why figure 12 of their PCF8574 datasheet is so obtuse. i recall i had some issue at the time with a design which led me to institute the double read; however, perhaps the problem was resolved elsewhere and i forgot to pull the 2nd read back out.

nevertheless, you could try it with one read -- just beware the Philips datasheet and the possibility that your manufacturing partner could substitute one part for the other. while i'd love to believe the Philips and TI parts are form/fit/function compatible, i have done enough designs to know that there is always some corner case lurking in wait for you.

as for the writes, if i understand the Philips datasheet, only the second is valid. see Figure 11.

jim / jds-pic
Dimlow



Joined: 24 Nov 2007
Posts: 9

View user's profile Send private message

PostPosted: Wed Jan 02, 2008 1:17 pm     Reply with quote

I interpreted from figure 11(Write) that data 1 is valid after the Acknowledge from the slave and is valid until the data2 acknowledge after this data2 is valid.

As for figure 12 (Read) this tells me how data is read when the pins are configured as inputs and also how the Interrupt works, first there is an interrupt because data has changed on the ports, the MCU reads the data (the Data is valid after the ack of the read command and before the data is sent to the mcu, also the Int is cleared) meanwhile as the data is being sent ,the data changes twice, data2 and data 3. again the int line is pulled low but this happens whilst the mcu is still receiving data1, at the end of data1 transmission the interrupt is cleared because of the Ack from data1 and the mcu has missed the data (data 2 and data3) and possibly the interrupt!

all this tell me that for send the io lines are valid on the ack of the data byte, and for read the data is valid after the ack of the read address.

i have not looked at the TI data sheets. In my last board i worked on, i used an 8574 for a keypad and only used one read and write with the Philips device.

Its what ever works for you i guess.

Regards

Gary
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> Code Library All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group