Problem interfacing pic16f1936 with i2c serial eeprom 24lc01b

Thread Starter

Interfacer

Joined Dec 15, 2012
9
I'm trying to read and write to an i2c serial eeprom (24lc01b) from a pic16f1936 to test functionality on an existing PCB design that has been implemented already. I'm using a library I got from the microchip website that was intended for the 16f877 using the hitech c compiler (Lite Mode). I just renamed constants (e.g. PSPIF to SSPIF; STAT_CKE to CKE) in the header file so it could compile. It compiles, I upload it to the board and no joy... Here's the header file:

Code:
/******************************************************************************************/
// Hardware I2C single master routines for PIC16F877
// for HI-TEC PIC C COMPILER.
//
// i2c_init  - initialize I2C functions
// i2c_start - issue Start condition
// i2c_repStart- issue Repeated Start condition
// i2c_stop  - issue Stop condition
// i2c_read(x) - receive unsigned char - x=0, don't acknowledge - x=1, acknowledge
// i2c_write - write unsigned char - returns ACK
//
// modified from CCS i2c_877 demo
//
// by
//
// Michael Alon - MICROCHIP FAE
// My E-mail michael@elina-micro.co.il
// www.elina-micro.co.il
// Tel: 972-3-6498543/4
// Fax: 972-3-6498745
// Elina Micro LTD .ISRAEL
//
/******************************************************************************************/

void i2c_init()
{
  TRISC3=1;           // set SCL and SDA pins as inputs
 TRISC4=1;

  SSPCON = 0x38;      // set I2C master mode
 SSPCON2 = 0x00;

 //file://SSPADD = 0x6;     // 400kHz bus with 10MHz xtal - use 0x0C with 20MHz xtal
 SSPADD = 10;            // 100k at 4Mhz clock

 CKE=1;     // use I2C levels      worked also with '0'
 SMP=1;     // disable slew rate control  worked also with '0'

 SSPIF=0;      // clear SSPIF interrupt flag
 BCLIF=0;      // clear bus collision flag
}

/******************************************************************************************/

void i2c_waitForIdle()
{
 while (( SSPCON2 & 0x1F ) | R_nW ) {}; // wait for idle and not writing
}

/******************************************************************************************/

void i2c_start()
{
 i2c_waitForIdle();
 SEN=1;
}

/******************************************************************************************/

void i2c_repStart()
{
 i2c_waitForIdle();
 RSEN=1;
}

/******************************************************************************************/

void i2c_stop()
{
 i2c_waitForIdle();
 PEN=1;
}

/******************************************************************************************/

int i2c_read( unsigned char ack )
{
 unsigned char i2cReadData;

 i2c_waitForIdle();

 RCEN=1;

 i2c_waitForIdle();

 i2cReadData = SSPBUF;

 i2c_waitForIdle();

 if ( ack )
  {
  ACKDT=0;
  }
 else
  {
  ACKDT=1;
  }
  ACKEN=1;               // send acknowledge sequence

 return( i2cReadData );
}

/******************************************************************************************/

unsigned char i2c_write( unsigned char i2cWriteData )
{
 i2c_waitForIdle();
 SSPBUF = i2cWriteData;

 return ( ! ACKSTAT  ); // function returns '1' if transmission is acknowledged
}

/******************************************************************************************/
and here's a snippet of my code:

Code:
#include <htc.h>
#include "877_i2c.h"

   
**************************** CONFIG FUSES *****************************/
//Set-up in MPLAB IDE... 

void Delay10xUs(unsigned char );
void    Delay10Us(void);   
void setup_main(void);
void led(unsigned char);
void ledflash_slow(unsigned char);
void ledflash_fast(unsigned char);



unsigned char cByteToRead;

//use these defintions for the ee library
//#define SCL            RC3
//#define SDA            RC4


/////////////Delay Routines - based on 8MHZ internal clock///////////////

    char _dcnt, _cnt;

void    Delay10Us()
    {_dcnt=1; 
    while(--_dcnt)continue; 
    }
   
void    Delay10xUs(unsigned char x) 
{
_cnt=x; 
while(--_cnt) 
Delay10Us();
}

void DelayMs(unsigned char p)
{
    do
    {
        Delay10xUs(16);
        Delay10xUs(16);
        Delay10xUs(16);
        Delay10xUs(16);
    }while(--p != 0);
}

void Delay250Ms(unsigned char q)
{
    do
    {
        DelayMs(250);
    }while(--q != 0); 
}

void DelayS(unsigned char r)
{
    do
    {
        Delay250Ms(4);
    }while(--r != 0);
}

/////////////End Delay Routines///////////////


/******************************************************************************************/

void write_ext_eeprom(unsigned char address, unsigned char data)
 {
   i2c_start();
   i2c_write(0xa0);
   i2c_write(address);
   i2c_write(data);
   i2c_stop();
   DelayMs(11);
}

/******************************************************************************************/

unsigned char read_ext_eeprom(unsigned char address)
{
   unsigned char data;

   i2c_start();
   i2c_write(0xa0);
   i2c_write(address);
   i2c_repStart();
   i2c_write(0xa1);
   data=i2c_read(0);
   i2c_stop();
   return(data);
}

/******************************************************************************************/


void main(void)
{
setup_main();
write_ext_eeprom(0x00, 0x01);

DelayS(1);

   
cByteToRead= read_ext_eeprom(0);

 ledflash_fast(cByteToRead);
DelayS(1);
}

void setup_main(void)
{
OSCCON=0b11110010;    //To select internal oscillator at 8MHz
ANSELA=0X02;     //Set only Pin 28 in Port A (RA1 - Bump input) to analog input
ANSELB=0;         //Set all analog pins in Port B to digital I/O 
TRISA=0X3F;        //Set RA6 and RA7 to outpur and the rest (RA5-0) to inputs
TRISB=0XFF;        //Set all I/O pins on PortB to inputs 
TRISC=0;        //Set all I/O pins on PortC to outputs
PORTC=0; 
}
Expecting an led to flash once... nothing... But the ledflash routine is working because I use it elsewhere in the program. It's as if it is stuck. Any code after trying to read eeprom does not run. I'm certain all the hardware is ok because when the micro is loaded with firmware. The EEPROM does it's job.
Notes:
- No pull up on SCL Line (must work without because other software makes it work);
- Internal oscillator used @8MHz (set in software using OSCCON register) - seems to work fine

Suspecting some interrupt related problem affecting the eeprom read routine. Like its getting stuck waiting for something from the eeprom. Maybe pic16f1936 interrupt settings are different (or does MSSP settings override?)
Please help.
 

Thread Starter

Interfacer

Joined Dec 15, 2012
9
Wow. You may want to read up on i2c first.

Fat chance you can get it to work without pull up.
First off. Thanks for the reply. I know for a fact that it works without this. As I said before, it is a fixed hardware design (product is on the market) therefore I cannot make any hardware changes (trying to design hardware test firmware), and it works with original firmware on the pic (I only have hex file - no source code). If you can't figure out why it is not working then we both might learn something new :) My first guess was maybe internal pull up?

A more pertinent question - has anyone tried something like this without the pull-up on on SCL? If I had spare parts I'd test this on a bread board. I'm more concerned with the validity of this code and correct setting up of the registers. I'm new to pic (used to arduino), and learning how to PROPERLY use new registers can be a daunting task, hence why I looked online for a library that would need minimal changes to make work.

By the way, the serial eeprom is a 5-Lead SOT-23 if that has any significance.
 

JohnInTX

Joined Jun 26, 2012
4,787
- No pull up on SCL Line (must work without because other software makes it work)
Some older ApNotes show SCL actively driven by the port instead of using a pullup. That can work if none of the I2C slaves ever have to stretch the clock to interrupt sending of data by the master. Most EEPROMs have page write buffers and do not stretch the clock so you see more active SCL in that application - I've used it myself in bit-banged I2C. But with anything else on the bus, including I2C analyzers, PIC MSSP slaves etc. you must conform to I2C spec. As the others have indicated, your HiTech library AND the Master Mode MSSP probably expects the pullup. 4.7K is usually a good starting point.

In your code I don't see you calling i2c_init(). That is necessary to config SCL/SDA as inputs. Be sure that the corresponding bits in the PORT registers are '0' - flipping TRIS bits is how most I2C libraries (with 2 pullups) work.
The read/write sequences look OK.
Note the the library code you show is blocking in nature. That means that problems will tend to lock up your code flow while the blocking code waits forever on something that won't happen. Combined with probably looking at the wrong register, the code below has the potential to hang things if something is preventing the IDLE conditon (SCL==SDA==1 after a stoP).

C:
/******************************************************************************************/
void i2c_waitForIdle()
{
while (( SSPCON2 & 0x1F ) | R_nW ) {}; // wait for idle and not writing
}
PSPIF is associated with the Parallel Slave Port on the '877. Not sure why it appears in I2C code.

If you don't have a scope it will be hard to debug this. You might be able to do some simple stuff without it such as check for SCL, SDA =1 after init, SCL=0 after sending start etc.

Good luck.
 

AlbertHall

Joined Jun 4, 2014
12,345
You could drive SCL high (i.e. output logic '1') instead of letting it float if you know that the devices on the I2C don't use clock stretching.
You will need to modify the I2C library to do that.
The part of library code you have shown includes these two lines:
TRISC3=1; // set SCL and SDA pins as inputs
TRISC4=1;
If there is no pull up on SCL then whichever of C3 and C4 is SCL is going to be left floating at an undefined level by this code. The I2C lines should be fed by open drain and the way this is done with the pic is to switch them between input (pulled high by the resistor) and output (with the output set to '0'.
You will have to go through the library code and change it so that instead of setting the SCL pin as an input, set it to output '1'.

The danger with that is if a device uses clock stretching then your master will be driving SCL high while the peripheral is pulling it low - not a good situation.

PS I see a reply (JohnInTX) while I was writing this. We agree.
 

Thread Starter

Interfacer

Joined Dec 15, 2012
9
Thanks JohnInTX. I did not notice that i2c_init() was missing there. I added it before calling eeprom routines. Now I'm seeing led flashing, but it is not flashing/reading the number i stored in address 0x00 (1). I think I stopped counting after 50. As for the blocking, maybe I can add a timeout routines to the library code (I don't know how long this would have to be though). You mentioned PSPIF register. Recall that I changed this to SSPIF thinking that that was the corresponding register for the pic16f1936. I was really thinking that I made a mistake by/when translating this library for 16f1936. Is that still a possibility looking at the header file I uploaded?

AlbertHall's idea sounds risky because I'm not sure about clocking stretching capabilities of the eeprom chip, and seems less like that is the problem now.

Any ideas as to what I can try to debug? The hardware is definitely working.
 

JohnInTX

Joined Jun 26, 2012
4,787
Thanks JohnInTX. I did not notice that i2c_init() was missing there. I added it before calling eeprom routines. Now I'm seeing led flashing, but it is not flashing/reading the number i stored in address 0x00 (1). I think I stopped counting after 50. As for the blocking, maybe I can add a timeout routines to the library code (I don't know how long this would have to be though). You mentioned PSPIF register. Recall that I changed this to SSPIF thinking that that was the corresponding register for the pic16f1936. I was really thinking that I made a mistake by/when translating this library for 16f1936. Is that still a possibility looking at the header file I uploaded?

AlbertHall's idea sounds risky because I'm not sure about clocking stretching capabilities of the eeprom chip, and seems less like that is the problem now.

Any ideas as to what I can try to debug? The hardware is definitely working.
@AlbertHall gives a good description of what you have to do if you are bit banging. Your code makes mention of using the MASTER mode (where the MSSP generates the I2C clock by itself) but your init does not show anything that configures SSPCON1 to set up master mode. If you are using the I2C master mode (as opposed to bit-banging as a master) you must use the pullups. That's how it works. The I2C master peripheral toggles SDA and SCL by switching the lines from an active low to HiZ with the pullup. That allows other I2C devices to stretch the clock, perform bus arbitration and the like. Its time to scab a resistor onto SCL.

What do you have to debug with? A 2 channel DSO is about the minimum. Without a scope you can do things like executing one line at a time and look at the voltages on the pins i.e. before sending Start both SCL and SDA =1. After sending Start (but before anything else) check that SCL is low. Stuff like that.

As far as using libraries, I personally write my own for the reasons you are seeing, especially when moving to similar but not exact peripherals. You don't know how good the author was, whether his constraints are compatible with yours (no blocking code, for example) etc. Finally, grabbing a library from somewhere in the hopes that you can get something working without really understanding what is going on is nearly always doomed to failure because when it doesn't work, you don't know where to start looking.

So, do you have a DSO?
 

TQFP44

Joined Sep 3, 2016
51
Have you checked the eeprom is address A0 , the A0-A2 pins at 0v
edit My error no A0-2 on the 24xx01
 
Last edited:

AlbertHall

Joined Jun 4, 2014
12,345
your init does not show anything that configures SSPCON1 to set up master mode.
The posted code does include
SSPCON = 0x38; // set I2C master mode
SSPCON2 = 0x00;

The pic16f1936 does not have a register called SSPCON. There are three SSPCON registers numbered 1,2 and 3 but maybe the header defines SSPCON as SSPCON1, in which case the above code does look like it sets the pic to use the built in I2C hardware which definitely won't work without a pull-up resistor.

If you want to use I2C without that resistor you cannot use the pics hardware, it will need to be bit banged. There is code on www that will do this.
 

Thread Starter

Interfacer

Joined Dec 15, 2012
9
@AlbertHall
After some consideration, I will retry this code with pullup when I get the parts (It will probably be with a 18f252 though). For now I want to shift the focus to the 'bit banging' option. I assume this means a software i2c routine independant of i2c hardware peripheral built into the pic. I got another library from the microchip site that looked something like this:
Code:
#include    <pic.h>
#include    "ee.h"

#define    SDA        RC4
#define    SCL        RC3
#define    TRUE    1
#define    FALSE    0


/******************************************************************************/
/***                           24C01A routines                              ***/
/******************************************************************************/
/******************************************************************************/
/*Name          : ee_delay                                                    */
/*Description   : Dummy procedure that delays for eerom                       */
/*                                                                            */
/*Author        : Cameron Pearce                                              */
/*Date          : 24/08/98                                                    */
/*Version       : 1.0                                                         */
/*                                                                            */
/*Inputs        : none                                                        */
/*Outputs       : none                                                        */
/*Destroys      : nothing                                                     */
/*Globals       : none                                                        */
/*                                                                            */
/*Comments      :                                                             */
/*Uses          : a byte                                                      */
/******************************************************************************/
void ee_delay(void)
{byte delay;

 for(delay=0;delay<10;delay++);

}
/******************************************************************************/
/*Name          : ee_byte_to_ee                                               */
/*Description   : Places the dat byte at address in the EEROM                 */
/*                                                                            */
/*Author        : Cameron Pearce                                              */
/*Date          : 24/08/98                                                    */
/*Version       : 1.0                                                         */
/*                                                                            */
/*Inputs        : dat, the byte written                                       */
/*                address, the byte address of the data                       */
/*Outputs       : Returns True if write was successful                        */
/*Destroys      : nothing                                                     */
/*Globals       : none                                                        */
/*                                                                            */
/*Comments      :                                                             */
/*Uses          : nothing                                                     */
/******************************************************************************/
bit ee_byte_to_ee(byte address, byte dat)
{
/* Returns True if write was successfully acknowledged */
 ee_start();                /* Send start sequence */

 ee_byte(0xa0);             /* Control byte (WRITE) */ 


 if (ack()) return(FALSE);     /* Test Acknowledge    */

 ee_byte(address);           /* Address Byte */

 if (ack()) return(FALSE);

 ee_byte(dat);               /* Data byte */

 if (ack()) return(FALSE);

 SCL=0;
 ee_delay(); 
 SDA=0;                     /* Stop condition */
 TRISC4=0;                    /* SDA OUTPUT     */
 ee_delay();
 SCL = 1;
 ee_delay();
 SDA = 1;
 ee_delay();
 return(TRUE);
}

/******************************************************************************/
/*Name          : ee_byte                                                     */
/*Description   : transmits the x byte to the eerom                           */
/*                                                                            */
/*Author        : Cameron Pearce                                              */
/*Date          : 25/08/98                                                    */
/*Version       : 1.0                                                         */
/*                                                                            */
/*Inputs        : x, the byte sent to the EEROM                               */
/*Outputs       : none                                                        */
/*Destroys      : nothing                                                     */
/*Globals       : none                                                        */
/*                                                                            */
/*Comments      :                                                             */
/*Uses          : 1 int                                                      */
/******************************************************************************/
void ee_byte(byte x)
{int i;

 i=0x80;
 do
  {
   SCL=0;
   TRISC4 =0;                /* SDA OUTPUT     */
   ee_delay();
   if (x&i) SDA=1;
    else SDA=0;
   ee_delay();
   SCL=1;
   ee_delay();
   i>>=1;
  }while(i!=0);
}
/******************************************************************************/
bit ack(void)
{ 
 SCL=0;                 /* Acknowledge         */
 ee_delay();
 TRISC4=1;                /* SDA Input        */
 ee_delay();
 SCL=1;
 ee_delay();            /* Test Acknowledge    */
 return(SDA);
}
/******************************************************************************/
/*Name          : ee_byte_from_ee                                             */
/*Description   : Gets a byte at address from the EEROM                       */
/*                                                                            */
/*Author        : Cameron Pearce                                              */
/*Date          : 25/08/98                                                    */
/*Version       : 1.0                                                         */
/*                                                                            */
/*Inputs        : dat, a pointer to the byte read                             */
/*                address, the byte address of the data                       */
/*Outputs       : Returns True if read was successful                         */
/*Destroys      : nothing                                                     */
/*Globals       : none                                                        */
/*                                                                            */
/*Comments      :                                                             */
/*Uses          : nothing                                                     */
/******************************************************************************/
bit ee_byte_from_ee(byte address,byte *dat)
{
/* Returns True if read was successfully acknowledged */

 ee_start();                /* Send start sequence */

 ee_byte(0xa0);             /* Write Control byte */

 if (ack()) return(FALSE);

 ee_byte(address);           /* Address Byte */

 if (ack()) return(FALSE);

 SCL=0;
 ee_delay();
 SCL=1;
 ee_delay();
 SDA=0;                     /* Start Condition */
 TRISC4=0;                    /* SDA OUTPUT       */
 ee_delay();            

 ee_byte(0xa1);          /* Read Control byte */

 if (ack()) return(FALSE);

 ee_data(dat);           /* Get the data byte */

 ack();

 SCL=0;                  /* Stop condition */
 ee_delay();
 SDA=0;
 TRISC4=0;                /* SDA OUTPUT       */
 ee_delay();
 SCL=1;
 ee_delay();
 SDA=1;
 return(TRUE);
}
/******************************************************************************/
/*Name          : ee_data                                                     */
/*Description   : receives a byte from the eerom                              */
/*                                                                            */
/*Author        : Cameron Pearce                                              */
/*Date          : 27/08/98                                                    */
/*Version       : 1.0                                                         */
/*                                                                            */
/*Inputs        : x, a pointer to the byte received                           */
/*Outputs       : none                                                        */
/*Destroys      : nothing                                                     */
/*Globals       : none                                                        */
/*                                                                            */
/*Comments      :                                                             */
/*Uses          : 1 int                                                      */
/******************************************************************************/
void ee_data(byte *x)
{int i;

 TRISC4=1;        /* SDA INPUT */
 *x=0;
 i=0x80;
 do
  {
   SCL=0;
   ee_delay();
   SCL=1;
   ee_delay();
   if (SDA) *x=(*x)|i;
   i>>=1;
  }while(i!=0);
}
/******************************************************************************/
void ee_start(void)
{
    TRISC3 = 0;
    TRISC4 = 0;            /* make SCL and SDA outputs     */
 SDA = 0;
 ee_delay();
 SCL = 1;
 ee_delay();
 SDA = 1;
 ee_delay();             /* Stop condition */

 SDA=0;
 ee_delay();             /* Start Condition */
}

void MasterAck(void)
{
 SCL=0;                 /* Acknowledge         */
 ee_delay();
 SDA=0;
 TRISC4=0;                /* SDA Output        */
 ee_delay();
 SCL=1;
 ee_delay();           
 SCL=0;
}

void ee_RepeatStart(void)
{
 SCL = 0;
 ee_delay();
 TRISC3 = 0; 
 TRISC4 = 0;            /* make SCL and SDA outputs     */
 ee_delay();
 SDA = 1;
 ee_delay();
 SCL = 1;
 ee_delay();
 SDA = 0;                        /* Restart now! */
}
Now. I looked at the datasheet for bus timing on master write and reception and compared it to timing from this code (I stepped through the whole library and generated some timing diagrams that were identical to those in the datasheet). This code didn't work either after running it. I suspected timing issue so I commented out the last two 'return FALSE' statements in the ee_byte_to_ee(byte address, byte dat). It seems I'm not even getting an acknowledge after sending the slave address (0xa0). I thought that maybe it is running to fast so I even decreased the internal oscillator frequency to 4MHz (Eeprom max speed is 400KHz so is the uC slow enough by looking at ee_delay routine?).

There is one thing that could also add some insight. Is it safe to say reading ack between rising and falling edge of the 9th clock pulse is correct? Furthermore will the slave (24lc01b) keep the SDA line low until it gets rising/falling edge from SCL that is driven by the master.

I'm sure we can agree that the pullup on SCL line should not be a problem now. If I can get the first acknowledge, that would be a good start I think.
 

AlbertHall

Joined Jun 4, 2014
12,345
The code as written uses the (correct) open drain outputs for SCL and SDA so this code will need a pull-up resistor. To use it without a resistor you will need to modify it to output a logic '1' on SCL instead of switching the pin to an input (allowing it to float).

I don't know how long the delay routine will take. It will differ depending on what code the compiler produces for this routine. Do you have a simulator (e.g. MPSIM) which can measure the actual delay time?
 

Thread Starter

Interfacer

Joined Dec 15, 2012
9
finally got it working after polling for ack instead of using delays and sampling the SDA line. I was using MPLABX 3.35 at this time (at home). I copied the source files and compiled at work (using MPLAB 8.36) successfully. Back to the problem of no ack!!!? I use the same version compiler hitech 9.65 Lite mode for both IDEs though. Is it possible for this to be a complication?
 

Thread Starter

Interfacer

Joined Dec 15, 2012
9
My mistake. the compiler at home was hi-tech v9.60. I tried it at work with v9.65 using mplabx and no joy. The problem was the compiler. Now I think I understand why microchip did away with this compiler. Using xc8 now for this application and it works perfectly as well. Perhaps I didn't even have to poll ack? I will try it and let you know. @dannyf the Phillips i2c standard does not need to be implemented to the nail on all devices. A generic software i2c would be a challenge I agree, but the datasheets for the device in question is a better guide. #StopDemotivatingNovices
 

JohnInTX

Joined Jun 26, 2012
4,787
My mistake. the compiler at home was hi-tech v9.60. I tried it at work with v9.65 using mplabx and no joy. The problem was the compiler. Now I think I understand why microchip did away with this compiler. Using xc8 now for this application and it works perfectly as well. Perhaps I didn't even have to poll ack? I will try it and let you know.
The IDE shouldn't matter if the compiler's project properties are set the same. If I had to guess, I would say that the different compiler versions generate different enough delay code to change the delay. Also the professional and 'lite' versions of the compiler may have different optimization levels.

Delays like Delay10Us() that take the clock into account are a better option. If your compiler library doesn't have one you could fake the short delays needed with inline assembler within the delay function.
#asm
nop
nop
..
#endasm
Check the compiler manual for the exact syntax. Note that if you want to get fancy, the delay between SDA and SCL for Start/stoP and data setup is shorter than the SCL width so you might use different delay routines for different things.

You need to be able poll ACK/NAK on the 9th SCL. Set SCL =1 and read SDA then set SCL=0; Besides knowing that the slave is actually there, its a way to know if previous writes have completed. If the EEPROM is still writing (it takes a few ms) you will get a NAK after writing the first address. When that happens, you write a stoP and try again later. You also could send rS I suppose but stoP is better.

The code shown is old and bare-bones. It might be a nice starting point but you'll likely have to punch it up a bit for a practical, robust interface. For example, the 24LC01 like a lot of I2C devices can get out of sync if you don't send it the proper number of SCL (like resetting the master in the middle of a transaction). Sometimes this results in SDA seemingly being 'stuck low'. To recover, be prepared to send a recovery sequence of up to 9 SCL, polling for SDA=1 each time. Then send stoP.

Good luck!
#StopDemotivatingNovices +1
 
Top