Interfacing a 24C32 32K Serial EEPROM with a PC Parallel Port copyright, Peter H. Anderson, Dept of Electrical Engineering Morgan State University, Baltimore, MD 21239, April 1, '97 This discussion shows how to use the parallel port to write to and read from the Microchip 24C32 32Kbit serial EEPROM. Although this discussion centers on the PC parallel port environment, it may be useful for designers in interfacing such I2C devices with other processors. Another discussion deals with interfacing with the Basic Stamp 2. The 24C32 uses the Philips 2-wire Inter IC (I2C) protocol and thus only two signal leads are required. Up to eight such EEPROMs may be connected on the same two line bus, with each device's A2, A1 and A0 terminals being strapped with a unique device code 0 through 7. Note that the 24C32 is arranged in 4096 bytes. Thus valid addresses are 0x0000 - 0x0fff. I have not used the 24C16 nor 24C65, but believe the programs which are presented will work with either. Of course, the highest address of the 24C16 is 0x07ff and the 24C65 is 0x1fff. The 24C32 is available from Digikey for nominally $3.00. A good data sheet is available from Microchip in PDF format at http://www.microchip.com. It is important to note that the SDA signal lead must be bidirectional, and thus the /STROBE lead on the control port of the PC Parallel Port is used. The output of the control port leads are open collector and thus a logic zero is a V_ce drop to ground while a logic one is essentially an open circuit. The external 10K pullup to +5VDC provides the logic one. This relatively high impedance to +5 is important as when a byte has been sent to the EEPROM, the EEPROM acknowledges by pulling the the SDA lead low. In reading from the serial EEPROM, the SDA lead is brought high by the PC and the state appearing on the /STROBE lead is read using the inportb instruction. As with all I2C devices, a data interchange is initiated with a "start" command; bringing SDA low while SCL is high and completed with a "stop" command; bringing SDA high while SCL is high. Each command from the PC master to the EEPROM slave begins with the byte; 1010 A2 A1 A0 R/W The leading 1010 is a unique code for EEPROM. A2, A1 and A0 correspond to the strapping of the A2, A1 and A0 terminals on the desired device. The final R/W bit is a logic 0 for a write operation and 1 for a read. After each byte is send by the master, the SDA lead is brought high (high impedance) to allow the EEPROM to acknowledge by bringing the lead low. Note that in function nack(), I read this bit but in most applications do not actually use the acknowlege bit. Writing data to the EEPROM consists of sending the command byte, followed by the high byte of the address, the low byte of the addresss and finally the data. In all cases bytes are send most significant bit first. Thus the sequence is; START 1010AAA0 N HHHHHHHH N LLLLLLLL N DDDDDDDD N A STOP 210 76543210 76543210 76543210 where N signifies the extra clock pulse to allow the EEPROM to send an acknowledgement. At the conclusion, the EEPROM may take some time to program the data and in function write_random_data, the EEPROM is polled up to ten times for an acknowledgement. If none is recieved, it is assummed the write failed and the program is exited with an appropriated message. In reading from the EEPROM, the command sequence begins with "start" command followed by the preface address byte. It may be somewhat confusing that the R/W bit is set to "write". This is followed by the high byte of the address and then the low byte of the address. Note that the "write" refers to writing the desired address to the EEPROM rather than the reading of the data. Another "start" command is then sent, with no intermediate "stop". This is followed by the the preface address byte. However, now, the R/W bit is set to "read". The byte is then read by the PC. Thus, the sequence; START 1010AAA0 N HHHHHHHH N LLLLLLLL N START 1010AAA1 N RRRRRRRR N STOP 210 76543210 76543210 76543210 where R indicates the PC reading a bit. Program 24C32_1.C illustrates writing data to a single location and then reading it back. The idea of the routine is to illustrate the lower loevel functions In Program 24C32_2.C, the implementation of the lower level functions have been omitted and a number of higher level functions are illustrated. 1. In function fill_all(), all locations are set to the specified data byte. This might be used to "erase" an EEPROM. I happened to use 0xff as meaning the data byte is unused. 2. In function find_first_available_byte(), each address location is checked to see if the data byte is the specified value and upon encountering the first location having the identified byte, the address is returned. 3. Data is then written to that location. 4. Finally, function dump() reads all locations up to the first location containing the identified data and displays it on the terminal and saves it to a file. These routines may be useful in logging data. When a data logger is first deployed, all locations might be set to some unique value which will not be used for the recording the actual data. For example, 0xff might be used to indicate a location is vacant. However, this implies that the value of the recorded data may not assume 0xff. In fact, this is a minor sacrifice as one rarely requires all 256 combinations associated with an 8-bit A/D. The data logger then does its thing, taking measurements and saving the data to EEPROM. However, assume power is momentarily lost by the processor. When power is again restored, we may want to avoid writing over the old data. Thus, the find_first_available() function permits the data to be tacked on the end of prior recorded data. In Program 24C32_3.c, higher level routines are illustrated to read an entire device and save it to a text file and to then program a device from the file. The obvious application is to copy an EEPROM. Of course, one assumes, it is a legal copy! /* * 24C32_1.C * * Illustrates how to write to and from a 24LC32 32K EEPROM. Writes data * 0x55 to location 0x0001 and then reads from the same location. * * Parallel Port 24LC32 * * Data0 (term 2) ------------------- SCL (term 6) ----- To Other * /STROBE (term 1)------------------ SDA (term 5) ----- 24LC32 Devices * * Note that the slave address is determined by A2 (term 3), A1 (term2) * and A0 (term 1) on 24LC32. The above SCL and SDA leads may be multipled * to eight devices, each strapped for a unique A2 A1 A0 setting. * * 10K pullup resistors to +5VDC are required on both signal leads. * * copyright Peter H. Anderson, MSU, March 28, '97 */ #include <stdio.h> #define DATA 0x0378 #define STATUS DATA+1 #define CONTROL DATA+2 typedef unsigned char byte; byte write_random_data(int device, int address, byte data); byte read_random_data(int device, int address); void out_byte(byte o_byte); byte in_byte(void); byte ack(byte device); byte nack(void); void start(void); void stop(void); void low_SDA(void); void high_SDA(void); void low_SCL(void); void high_SCL(void); void main(void) { byte i_byte; if(write_random_data(0x00, 0x0001, 0x55)!=0) /* write to device 0, adr 0x001, data 0x55. If not acknowledged the ** write failed */ { printf("Write failed.\n"); exit(0); } i_byte=read_random_data(0x00, 0x0001); /* read data from the same location and display it */ printf("%x\n", i_byte); } byte write_random_data(int device, int address, byte data) /* write specified data to specified address on specified device 0 - 7 ** returns 0 if acknowledge received from EEPROM. 1 if failure */ { byte ack_bit, n; start(); out_byte(0xa0 | (device<<1)); /* send preface 1010 a2 a1 a0 0 */ nack(); out_byte(address>>8); /* send high byte of address */ nack(); out_byte(address&0xff); /* send low byte of address */ nack(); out_byte(data); /* and finally the actual data */ nack(); stop(); for(n=0; n<10; n++) /* look for acknowledge - up to ten times */ { ack_bit=ack(device); if (ack_bit==0) { break; } } return(ack_bit); /* 0 if successful */ } byte read_random_data(int device, int address) /* read from specified address. return fetched data */ { byte i_byte; start(); out_byte(0xa0 | (device<<1)); /* preface is a write; ls bit = 0 */ nack(); out_byte(address>>8); /* send high and low byte of address */ nack(); out_byte(address&0xff); nack(); start(); /* start with no stop */ out_byte(0xa0 | (device<<1) | 0x01); /* preface is now read. 1010 a2 a1 a0 1 */ nack(); i_byte=in_byte(); /* fetch the byte */ nack(); stop(); return(i_byte); } void out_byte(byte o_byte) /* shift out byte, beginning with most significant bit */ { int n; for(n=7; n>=0; n--) { /* note SCL is low during transitions on SDA */ if (((o_byte >>n) & 0x01) == 0) { low_SDA(); } else { high_SDA(); } high_SCL(); low_SCL(); } } byte in_byte(void) /* fetch byte, most significant byte first */ { byte i_byte=0x00; int n; high_SDA(); for (n=0; n<8; n++) { high_SCL(); i_byte=(i_byte << 1) | ((inportb(CONTROL)^0x01)&0x01); /* note inversion on least significant bit of control port */ low_SCL(); } return(i_byte); } byte ack(byte device) { byte ack_bit; out_byte(0xa0 | (device<<1)); /* send preface */ ack_bit=nack(); /* look for acknowledgement */ return(ack_bit); } byte nack(void) { byte ack_bit; high_SDA(); high_SCL(); if (((inportb(CONTROL)^0x01)&0x01)==0) { ack_bit=0; } else { ack_bit=1; } low_SCL(); return(ack_bit); } void start(void) /* bring SDA high to low while SCL is high */ { high_SDA(); high_SCL(); low_SDA(); /* bring SDA low while clock is high */ low_SCL(); } void stop(void) /* bring SDA low to high while SCL is high */ { low_SDA(); high_SCL(); high_SDA(); /* bring SDA high while clock is high */ low_SCL(); } void low_SDA(void) { outportb(CONTROL, 0x00^0x01); /* inversion required on ls bit of control port */ } void high_SDA(void) /* makes output high impedance */ { outportb(CONTROL, 0x01^0x01); } void low_SCL(void) { outportb(DATA, 0x00); } void high_SCL(void) { outportb(DATA, 0x01); } /* * 24C32_2.C * * Fills all locations in EEPROM with fill_value. In this case 0xff. * * Writes 0x200 data values to EEPROM. Normally data values might be result * of an A/D reading or similar. In this example, data values are generated * using the function random(). * * Content of EEPROM up to first occurance of the fill_value is then * displayed on terminal. * * copyright Peter H. Anderson, MSU, March 28, '97 */ #include <stdio.h> #include <stdlib.h> #define DATA 0x0378 #define STATUS DATA+1 #define CONTROL DATA+2 typedef unsigned char byte; byte fill_all(int device, byte fill_value); int find_first_available(int device, byte fill_value); void dump(int device, byte fill_value); /* The following functions are the same as in program 24C32_1.C */ byte write_random_data(int device, int address, byte data); byte read_random_data(int device, int address); void out_byte(byte o_byte); byte in_byte(void); byte ack(byte device); byte nack(void); void start(void); void stop(void); void low_SDA(void); void high_SDA(void); void low_SCL(void); void high_SCL(void); void main(void) { int n, device=0x00, address; byte data, fill_value=0xff; fill_all(0x00, fill_value); /* fill all locations with 0xff */ for (n=0; n<0x200; n++) /* write 0x200 data samples */ { data = (byte) random(0xff); /* generate a random number 0x00 - 0xfe */ address=find_first_available(device, fill_value); /* find first "empty location" */ write_random_data(device, address, data); /* and write data to that location */ } dump(device, fill_value); /* display content of eeprom up to ** first empty location */ } byte fill_all(int device, byte fill_value) /* fills all locations in specified address with same specified data */ { int address; for (address=0x0000; address<=0x0fff; address++) { write_random_data(device, address, fill_value); } } int find_first_available(int device, byte fill_value) /* returns first address location containing the specified data ** if specified data is not found, program exits with an error message */ { int address; byte value; for(address=0x0000; address<=0x0fff; address++) { value=read_random_data(device, address); if (value==fill_value) { return(address); } } printf("Specified Data Value Not Found\n"); exit(-1); } void dump(int device, byte fill_value) { /* displays content of EEPROM until specified data is encountered */ int address; byte value; for(address=0x0000; address<=0x0fff; address++) { value=read_random_data(device, address); if (value==fill_value) { break; /*done*/ } printf("%x\t", value); if ((address+1)%8==0) { printf("\n"); /* new line every 8 locations */ } } } /* * 24C32_3.C * * Illustrates how to read the full content of a 24LC32 32K EEPROM and * save to a text file. This might be useful as a primitve way of * "uploading" a data logger or in copying EEPROMs. * * Content of file is then written to a second EEPROM. * copyright Peter H. Anderson, MSU, March 28, '97 */ #include <stdio.h> #include <conio.h> #define DATA 0x0378 #define STATUS DATA+1 #define CONTROL DATA+2 typedef unsigned char byte; /* The implementation of the following functions are contained in ** Program 24C32_1.C */ byte write_random_data(int device, int address, byte data); byte read_random_data(int device, int address); void out_byte(byte o_byte); byte in_byte(void); byte ack(byte device); byte nack(void); void start(void); void stop(void); void low_SDA(void); void high_SDA(void); void low_SCL(void); void high_SCL(void); void main(void) { int device=0, address, n; byte value, values[8]; char str[80]; FILE *f; f=fopen("eeprom.dta", "wt"); for (address=0x0000; address<0x0fff; address++) /* read each location and write to a text file - 8 values per line */ { value=read_random_data(device, address); printf("%x\t", value); fprintf(f, "%x\n", value); if((address+1)%8 == 0) /* a new line every 8 values */ { printf("\n"); fprintf(f, "\n"); } } fclose(f); printf("\n\nDone. Hit any key to program another EEPROM\n"); while(!kbhit()) /* loop until a key is hit */ ; f=fopen("eeprom.dta", "rt"); /* open the file for reading */ address=0x0000; while(fgets(str, 80, f)!=NULL); { /* get one line of 8 values and write to EEPROM */ sscanf(str, "%x %x %x %x %x %x %x %x", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5], &values[6], &values[7]); for(n=0; n<8; n++) { write_random_data(device, address, values[n]); ++address; } } fclose(f); printf("\Done!\n\n"); }