PIC16F724 communication with PCA9622 using I2C

Thread Starter

nic6911

Joined Jun 22, 2012
24
Hi all.
I have recieved a circuitboard that i have to program to do some simple stuff. The only problem is that i have very little experience in C. I am used to programming PLC's in ladder.

The board has 30 led's that are connected with two PCA9622 chips controlled by the PIC. There are 17 inputs and two outputs on the board. The first 15 led's are green and the last 15 are yellow.
LED1 and LED16 are combined in one, LED2 and 17 are combined and so on..

The function should be this:
If input 1 gets high led1 or led16 goes on, if in2 gets high led2 or led17 goes on and so on. So i should be able to chose if it is the green or the yellow led that is lit whenever input x is high.
If input 16 goes high all yellow led's should be lit to see if they work.

The two outputs should be set high if certain led's are on. I should be able to write which led's would set one of the outputs high.

That is basically it.

I have read through the datasheet of the PCA9622 and also the PIC. I have programmed this type of PIC before to drive som led´s, but not through i2c.

I am very confused on how to deal with this.
I have some code, but i am not sure how to do this. The first thing is to be able to turn on the led's individually.

So far i have understood this is how it goes:
Start
1. adress of slave (PCA9622)
2. go to mode 1 register (don't know why, but that is what i get from the datasheet)
3. ReStart
4. adress of slave again
5. and know i am lost?!? :S
Rich (BB code):
#include    <htc.h>
 
#define _XTAL_FREQ 16000000
 
 
#define SCL     RC3 // I2C bus
#define SDA     RC4 //
 
 
#define green 0x14;
#define red 0x15;
 
 
void i2c_dly(void)
{
}
 
 
void i2c_start(void)
{
  SDA = 1;             // i2c start bit sequence
  i2c_dly();
  SCL = 1;
  i2c_dly();
  SDA = 0;
  i2c_dly();
  SCL = 0;
  i2c_dly();
}
 
 
void i2c_stop(void)
{
  SDA = 0;             // i2c stop bit sequence
  i2c_dly();
  SCL = 1;
  i2c_dly();
  SDA = 1;
  i2c_dly();
}
 
 
 
 
main(void){
i2c_start();              // send start sequence
SDA = green;            //adress for the green led driver
SDA = 0x00;             //Mode 1 register
_delay_us(600);
i2c_start();            //restart
SDA = green;            //adress for the green led driver
SDA = 0x14;
 
 
i2c_stop();               // send stop sequence
 
 
}
I have a schematic for the board attached to the thread.

But the datasheet for PCA9622 is here
http://www.nxp.com/documents/data_sheet/PCA9622.pdf

Any help for the part with the i2c and PCA9622 would be much appreciated!

Regards Nic
 

Attachments

I have used the PCA9635, which is supposed to be same in a lot of ways to the PCA9622. If you can bear with GCBasic syntax, I can lay out some code snippets which may help you along in initializing and writing to the led driver. Good luck :)
Rich (BB code):
Dim PWM(16)
#define Mode1 0x00 
#define Mode2 0x01  
PWM(1) = 0x02
PWM(2) = 0x03
PWM(3) = 0x04
PWM(4) = 0x05
;etc.
...
...
;Standard reset
    write8bit(PCA9635write, MODE1, 0x00)
;    All addressing acceptable
    write8bit(PCA9635write, MODE2, 0x00)

    write8bit(PCA9635write, GRPPWM, 0xFF)
    write8bit(PCA9635write, GRPFREQ, 0x00)
    write8bit(PCA9635write, LEDOUT0, 0xFF)
    write8bit(PCA9635write, LEDOUT1, 0xFF)
    write8bit(PCA9635write, LEDOUT2, 0xFF)
    write8bit(PCA9635write, LEDOUT3, 0xFF)
    write8bit(PCA9635write, GRPFREQ, 0x00)
...
...
Main:
For index = 1 to 16
    If index = 1 Then PWMvalue = 15
    write8bit(PCA9635write, PWM(index), PWMvalue)
    If PWMvalue = 255 Then goto skip
    PWMvalue = (index * 16) - 1
skip:
wait 2 10ms
Next
...
...
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
hmm.. Thanks for the reply. But i still don't get it.

I have programmed a bit in arduino - and that is far less complex than this C in mplab.

Anyone got a link to a place where they have programmed a pic to interface with the PCA9622? A tutorial? anything?

When i look at it, i become more certain that this is kind of a though thing for a beginner like me. Maybe to though...
 
You need pullups on SDA, SDC like 4.7kΩ. The OE pin needs to be active low.

I am no help with Hitech C, and no links either. When I had the PCA9635, it was brand new and had to slog it out with the data sheet. The code snippets that I supplied did work, but by no means did I master all the functions of this chip.

Really important that the Mode1 (and possibly Mode2?) be initialized for the chip. You send 0x14 to Mode1 which means it is in low power mode and subaddress 2 is active. I recommend writing 0x00 like I have, so that dimming is possible and just ignore the subaddress stuff altogether for now. Set up the other registers that I show too. They may be redundant or unnecessary, but it works, revisit later after first blinky arrives.

Once you have initialized the chip, then it is time to write to the PWM registers in Main, or While(1) :)

When working with a new IC chip or even a serial data protocol like I2C it is always beneficial to begin with the lowest level of functionality. Get the led driver chip going before worrying about which led comes on when which button is pushed.
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
Yes, my thought was also to get the i2c bus to work before thinking of anything else. I have tried the whole night yesterday and a couple of hours now today. And i am out of ideas. I can't write it as simple as you do in gcbasic when using mplab and C...

You must have some sort of library? you just write
Rich (BB code):
write8bit(PCA9635write, LEDOUT0, 0xFF)
I don't know GCbasic, but you must have a library to be able to do this?

I have tried using some of this http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html
But can't get that to work either...

Damn i'm in trouble here :S
 
What I have written in GCBasic was for the hardware MSSP module of the PIC. Yes it is a library, but no different than the referenced tutorial, just shorthand for:
Rich (BB code):
Start
tx(address)
tx(chip register)
tx(value)
Stop
What values are used for the pullups, is the OE pin shorted or pulled low by the micro pin, can you disconnect the second chip to eliminate bus contention.

A cheapo digital logic probe can come in handy, just to see if data is being sent out or received. I2C is a synchronous serial protocol, so you can increase the delays to really slow down data so it can be seen by the logic tool.

One thing that is not obvious in tutorial is how the data rate is arrived at, as no indication what the OSC speed is. This is not likely to affect the FM+ chip as it can go up to 1Mhz. Surely there is an I2C library and example (folder) in the HitechC download?

I learned I2C with a Microchip 24L256, you write a value to a register, then read it back on a LCD, simple as it gets.
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
Hi. Yeah if there were a library for i2c, it would be much easier i guess.
It sounds so simple :)
In the meantime i have found this http://www.hobbytronics.co.uk/hi-tech-c-i2c-master
I guess that is just what i need!?

Now it just worries me that you say that there should be resistors on the SDA and SCL lines..? And what is the OE? is that the EN pin on the RCA9622 you mean?
 
Wouldn't hurt to try that library, just make sure the SCL and SDA pins on your micro are tristated, that's why you need the pullup resistors on those lines!! I have seen people who have used digital outputs only, it doesn't conform to the standard and would only work for one chip on the bus. Look at the data sheet for the PCA, it shows 10k pullups.
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
Okay i will have to make an adjustment with the resistors then.
When i try to use that code in the link i get error messages

undefined identifier "SSPCON1"
undefined identifier "SSPCON2"
undefined identifier "SEN"
undefined identifier "RSEN"
undefined identifier "PEN"
undefined identifier "RCEN"
undefined identifier "ACKDT"
undefined identifier "ACKEN"

Any idea what that could be? is it because of me using a different pic than in the example?
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
I've found a library for hitech C. But again i can't test it before i get the pull-up's on the SDA and SCL lines. The OE will be controlled by digital outputs. I don't know why but the designer has set the OE for each PCA to an output so that it can be disabled. But it doesn't make sense disabling the PCA's ? They should always be enabled? they have indivdual adresses, so i don't see the point?
Do you think there is a good reason for doing this??

Regards
 
The OE does not disable the chip, just the outputs. That means you can PWM all the leds, or turn them on or off, at once external to what the PCA is doing software wise. The OE pin on shift registers like the 74 HC595 works like that too.

One reason I see to use the OE pin would be like for an ambient light sensor to adjust brightness. Another possible reason would be for using the micro to do some lighting effects based on led grouping per chip, less overhead, or more convenient that way? Most all the time you see these pins hardwired to the ON state.
 
Okay i will have to make an adjustment with the resistors then.
When i try to use that code in the link i get error messages

undefined identifier "SSPCON1"
undefined identifier "SSPCON2"
undefined identifier "SEN"
undefined identifier "RSEN"
undefined identifier "PEN"
undefined identifier "RCEN"
undefined identifier "ACKDT"
undefined identifier "ACKEN"

Any idea what that could be? is it because of me using a different pic than in the example?
O.K. then I see the PIC16f724 only has the SSP module, which means it only has the I2C slave function in hardware. The example code is for a chip that has the MSSP module, which means it also has the master mode in hardware. So that will not work, you will be looking for a software I2C solution.
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
But it does not make sense trying anything as long as i do not have the resistors on SDA and SCL, it wouldn't work anyway. so i will get the resistors mounted first and then try again.

I can't see why i should use the OE pin. I know it is used with shift registers and that makes sense.
Those that made this panel knew that it is only for on and off operation of the led's. But anyway, i will just pull it high in the code.
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
I got the two resistors mounted as also shown in the datasheet for the pic.
The OE pin needs to be set as low right?

Now i am still not getting any good results with my programming...

Rich (BB code):
#include <htc.h>
#define _XTAL_FREQ 16000000
#define SCL     TRISC3 // I2C bus
#define SDA     TRISC4 //
#define SCL_IN  RC3    //
#define SDA_IN  RC4    //
 
void i2c_dly(void)
{
}
void i2c_start(void)
{
  SDA = 1;             // i2c start bit sequence
  i2c_dly();
  SCL = 1;
  i2c_dly();
  SDA = 0;
  i2c_dly();
  SCL = 0;
  i2c_dly();
}
void i2c_stop(void)
{
  SDA = 0;             // i2c stop bit sequence
  i2c_dly();
  SCL = 1;
  i2c_dly();
  SDA = 1;
  i2c_dly();
}
unsigned char i2c_rx(char ack)
{
char x, d=0;
  SDA = 1; 
  for(x=0; x<8; x++) {
    d <<= 1;
    do {
      SCL = 1;
    }
    while(SCL_IN==0);    // wait for any SCL clock stretching
    i2c_dly();
    if(SDA_IN) d |= 1;
    SCL = 0;
  } 
  if(ack) SDA = 0;
  else SDA = 1;
  SCL = 1;
  i2c_dly();             // send (N)ACK bit
  SCL = 0;
  SDA = 1;
  return d;
}
bit i2c_tx(unsigned char d)
{
char x;
static bit b;
  for(x=8; x; x--) {
    if(d&0x80) SDA = 1;
    else SDA = 0;
    SCL = 1;
    d <<= 1;
    SCL = 0;
  }
  SDA = 1;
  SCL = 1;
  i2c_dly();
  b = SDA_IN;          // possible ACK bit
  SCL = 0;
  return b;
}
 
 
main(void){
TRISB = 0;
PORTB = 0x00;   //OE pin low
SDA = SCL = 1;
SCL_IN = SDA_IN = 0;
i2c_start();              // send start sequence  
i2c_tx(0x28);   //adress
i2c_tx(0x00);   //load mode1
i2c_tx(0x00);   //00
i2c_stop();  
i2c_start();              // send start sequence
i2c_tx(0x28);   //adress
i2c_tx(0x14);   //led0
i2c_tx(0x55);   //turn all led on
i2c_stop(); 
}
I thought this would do it, but i am not getting any response from the led's...

Damn it, this just won't work for me :S
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
Hi again.
I found out that someting else is wrong.
I use the following code:
Rich (BB code):
#include <htc.h>
#define _XTAL_FREQ 16000000
#include  "i2c.h"
/*
 * I2C functions for HI-TECH PIC C - master mode only
 */
/*
 *  TIMING - see Philips document: THE I2C-BUS SPECIFICATION
 */

/*
 *  Send stop condition
 *    - data low-high while clock high
 */
void
i2c_Stop(void)
{
 /* don't assume SCL is high on entry */
 SCL_LOW();
 SDA_LOW();     /* ensure data is low first */
 
 __delay_us(I2C_TM_DATA_SU);
 SCL_DIR = I2C_INPUT;  /* float clock high */
 __delay_us(I2C_TM_STOP_SU);
 SDA_HIGH();     /* the low->high data transistion */
 __delay_us(I2C_TM_BUS_FREE); /* bus free time before next start */
 SDA_DIR = I2C_INPUT;  /* float data high */
 return;
}
/*
 *  Send (re)start condition
 *    - ensure data is high then issue a start condition
 *    - see also i2c_Start() macro
 */
void
i2c_Restart(void)
{
 SCL_LOW();     /* ensure clock is low */
 SDA_HIGH();     /* ensure data is high */
 __delay_us(I2C_TM_DATA_SU);
 SCL_DIR = I2C_INPUT;  /* clock pulse high */
 __delay_us(I2C_TM_SCL_HIGH);
 SDA_LOW();     /* the high->low transition */
 __delay_us(I2C_TM_START_HD);
 return;
}
/*
 *  Send a byte to the slave
 *    - returns true on error
 */
unsigned char
i2c_SendByte(unsigned char byte)
{
 signed char i;
 for(i=7; i>=0; i--)
 {
  SCL_LOW();     /* drive clock low */
  
  /* data hold time = 0, send data now */
        SDA_DIR = ((byte>>i)&0x01);
        if ((byte>>i)&0x01) {  /* bit to send */
   SDA_HIGH();
        }else {
   SDA_LOW();
        }
  __delay_us(I2C_TM_DATA_SU);
  SCL_DIR = I2C_INPUT;  /* float clock high */
  if(i2c_WaitForSCL())  /* wait for clock release */
   return TRUE;   /* bus error */
  __delay_us(I2C_TM_SCL_HIGH); /* clock high time */
 }
 
 return FALSE;
}
/*
 *  send an address and data direction to the slave
 *    - 7-bit address (lsb ignored)
 *    - direction (FALSE = write )
 */
unsigned char
i2c_SendAddress(unsigned char address, unsigned char rw)
{
 return i2c_SendByte(address | (rw?1:0));
}
/*
 *  Check for an acknowledge
 *    - returns ack or ~ack, or ERROR if a bus error
 */
signed char
i2c_ReadAcknowledge(void)
{
 unsigned char ack;
 SCL_LOW();      /* make clock is low */
 SDA_DIR = I2C_INPUT;   /* disable data line - listen for ack */
 __delay_us(I2C_TM_SCL_TO_DATA); /* SCL low to data out valid */
 SCL_DIR = I2C_INPUT;   /* float clock high */
 __delay_us(I2C_TM_DATA_SU);
 ack = SDA;      /* read the acknowledge */
 /* wait for slave to release clock line after processing byte */
 if(i2c_WaitForSCL())
  return I2C_ERROR;
 return ack;
}
/*
 *  Read a byte from the slave
 *    - returns the byte, or I2C_ERROR if a bus error
 */
int
i2c_ReadByte(void)
{
 unsigned char i;
 unsigned char byte = 0;
 for(i=0; i<8; i++)
 {
  SCL_LOW();     /* drive clock low */
  __delay_us(I2C_TM_SCL_LOW); /* min clock low  period */
  SDA_DIR = I2C_INPUT;  /* release data line */
  SCL_DIR = I2C_INPUT;  /* float clock high */
  if(i2c_WaitForSCL())
   return I2C_ERROR;
  __delay_us(I2C_TM_SCL_HIGH);
  byte = byte << 1;  /* read the next bit */
  byte |= SDA;
 }
 return (int)byte;
}
/*
 *  Send an (~)acknowledge to the slave
 *    - status of I2C_LAST implies this is the last byte to be sent
 */
void
i2c_SendAcknowledge(unsigned char status)
{
 SCL_LOW();
 if ( status & 0x01) {
  SDA_LOW();    /* drive line low -> more to come */
 }else { 
  SDA_HIGH();
 }
 __delay_us(I2C_TM_DATA_SU);
 SCL_DIR = I2C_INPUT;  /* float clock high */
 __delay_us(I2C_TM_SCL_HIGH);
 return;
}
/*
 *  Send a byte to the slave and acknowledges the transfer
 *    - returns I2C_ERROR, ack or ~ack
 */
signed char
i2c_PutByte(unsigned char data)
{
 if(i2c_SendByte(data))
  return I2C_ERROR;
 return i2c_ReadAcknowledge(); /* returns ack, ~ack */
}
/*
 *  Get a byte from the slave and acknowledges the transfer
 *    - returns true on I2C_ERROR or byte
 */
int
i2c_GetByte(unsigned char more)
{
 int byte;
 if((byte = i2c_ReadByte()) == I2C_ERROR)
  return I2C_ERROR;
 i2c_SendAcknowledge(more);
 return byte;
}
/*
 *  Send an array of bytes to the slave and acknowledges the transfer
 *    - returns number of bytes not successfully transmitted
 */
int
i2c_PutString(const unsigned char *str, unsigned char length)
{
 signed char error;
 while(length)
 {
  if((error = i2c_PutByte(*str)) == I2C_ERROR)
   return -(int)length;     /* bus error */
  else
   if(error)
    return (int)length;     /* non acknowledge */
  str++;
  length--;
 }
 return FALSE;         /* everything OK */
}
/*
 *  Reads number bytes from the slave, stores them at str and acknowledges the transfer
 *    - returns number of bytes not successfully read in
 */
unsigned char
i2c_GetString(unsigned char *str, unsigned char number)
{
 int byte;
 while(number)
 {
  if((byte = i2c_GetByte(number-1)) == I2C_ERROR)
   return number;        /* bus error */
  else
   *str = (unsigned char)byte;
  str++;
  number--;
 }
 return FALSE;          /* everything OK */
}
/*
 *  Opens communication with a device at address. mode
 *  indicates I2C_READ or I2C_WRITE.
 *    - returns TRUE if address is not acknowledged
 */
unsigned char
i2c_Open(unsigned char address, unsigned char mode)
{
 i2c_Start();
 i2c_SendAddress(address, mode);
 if(i2c_ReadAcknowledge()) 
  return TRUE;
 return FALSE;
}
/*
 *  wait for the clock line to be released by slow slaves
 *    - returns TRUE if SCL was not released after the
 *      time out period.
 *    - returns FALSE if and when SCL released
 */
unsigned char
i2c_WaitForSCL(void)
{
 /* SCL_DIR should be input here */
 if(!SCL)
 {
  __delay_us(I2C_TM_SCL_TMO);
  /* if the clock is still low -> bus error */
  if(!SCL)
   return TRUE;
 }
 return FALSE;
}
void
i2c_Free()
{
 unsigned char ucI;
 SDA_DIR=I2C_INPUT;
 for(ucI=0;ucI!=9;ucI++)
 {
  SCL_HIGH();
  __delay_us(5);
  SCL_LOW();
  __delay_us(5);
 }
}
unsigned char i2c_read(unsigned char ucAdr)
{
 unsigned char ucDat;
 if (i2c_ReadFrom(ucAdr)==0)
 {
  ucDat=i2c_GetByte(I2C_MORE);
  i2c_Stop();
 }
 return(ucDat);
}

main(void){
TRISB = 0;
PORTB = 0x00;   //OE pin LOW 
i2c_WriteTo(0x28);
i2c_PutByte(0x00);
i2c_PutByte(0x00);
i2c_Stop(); 
i2c_WriteTo(0x28);
i2c_PutByte(0x14);
i2c_PutByte(0x55);
i2c_Stop();  
i2c_WriteTo(0x2A);
i2c_PutByte(0x15);
i2c_PutByte(0x55);
i2c_Stop();   
i2c_WriteTo(0x2A);
i2c_PutByte(0x16);
i2c_PutByte(0x55);
i2c_Stop(); 
i2c_WriteTo(0x2A);
i2c_PutByte(0x00);
i2c_PutByte(0x00);
i2c_Stop();
i2c_WriteTo(0x2A);
i2c_PutByte(0x17);
i2c_PutByte(0x55);
i2c_Stop(); 
}
It worked fine at first. I have light in the led's. But it seems like it stays in some infinite loop somewhere. It stays locked in this position. Sometimes when i transfer a edited program it doesn't work - then i unplug the power and wait for 10 seconds and plug it back in, then i get light! But if i plug it in after lets say 3 seconds, then i don't get light!?

It seems like i am stuck in a loop?
 
Congrats, looks like you about have it. Sounds like the bus isn't getting released somewhere. I don't see any i2c_Start()'s. Double check the previously mentioned registers for initialization. That's just to make sure you are starting in the proper condition if it varies from the default 0x00???

For me it is more clear by defining the registers as constants like mode1, and pwm1, pwm2 etc. When doing lighting effects, then an array for pwm(1), pwm(2), etc. will come in handy when doing the multitudes of code loops .
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
The i2c_start is included in the i2c_writeto, but maybe i will try to make it more simple and use start instead. This is a sample code from hitech C i found.
It works fine, but as mentioned it gets stuck somewhere.. I will have a look at the mode1 and 2 registers for the initialization.

I am really new to C so i don't even mess around with defining things as constants or creating arrays.

Also the only thing i should be able to do, is to turn led's on and off, no blinkin or dimming...

Thanks for the help so far. I will try your suggestion and post again later.
 

Thread Starter

nic6911

Joined Jun 22, 2012
24
Just looked at the datasheet for the PCA. The Mode1 is okay as 0x00 and mode2 should just be left default as i see it.

But it is weird that it gets stuck...
 
Back in post #2 I gave other init states of registers. It looks like the GRPPWM and GRPFREQ registers just repeat the defaults. The init power up on the led drivers is off, and that is why LEDOUT0-3 are set to 0xFF. This type of troubleshooting is unappetizing, because I cannot duplicate the setup.

Edit: beg your pardon I see thoughs registers are supposed to be turned full on with your code.
 
Last edited:

Thread Starter

nic6911

Joined Jun 22, 2012
24
It doesnt have something to do with my OE pin? i set it low at the beginning of main... It doesn't help using the lower level code like start, address, and send byte.
The slave sends ack after data is recieved, but in my code i don't look at this, could that do something??
 
Top