/********************************************************************* * jl16-7.c - Demonstrate reading and writing to an I2C EEPROM * * This program store some information in a 24LC256 (32k x 8) * I2C EEPROM and then read it back and reports the results out * the RS-232 interface. * If the button on PTA1 is pressed it writes and then reads. * If the button is not pressed it only does the reading part. * * NOTE: This program only works on the JL16 (or later) models * that have I2C interfacing capability. * * The I2C hardware on the JL16 has a number oddities that make using * it more difficult than that on chips like the Atmel ATmega168A. * To see our notes on how the JL16 I2C works (and doesn't work) * go to "http://ee.usc.edu/ee459/library/jl16-iic" * * Revision History * Date Author Description * 01/28/08 A. Weber First Release * 02/12/08 A. Weber Clean up code a bit * 03/03/08 A. Weber More code cleanups * 04/22/08 A. Weber Added "one" variable to make warning go away * 04/21/11 A. Weber Changed strings to make read/write clearer * 09/01/11 A. Weber Changes to I2C routines * 05/03/12 A. Weber Combined I2C write and read routines into one function * 05/16/12 A. Weber Changed I2C code that checks for NAK/ACK to * something that might actually work (sort of). * 07/09/12 A. Weber Removed code to set security bytes * 05/29/13 A. Weber Renamed CWJL-7.c to jl16-7.c *********************************************************************/ #include #include /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #include void i2c_init(unsigned char); extern unsigned char i2c_io(unsigned char, unsigned char *, unsigned int, unsigned char *, unsigned int, unsigned char *, unsigned int); unsigned char rdprom(unsigned char *, int, word); unsigned char wrprom(unsigned char *, int, word); void sci_init(unsigned char, unsigned char); void sci_out(unsigned char); void sci_outs(unsigned char *); void del1m(int); /* Address of the EEPROM on the I2C bus */ #define EEPROM_ADDR 0xA0 /* Address where we store test data in the EEPROM */ #define DATA_ADDR 500 /* Test strings to store in the EEPROM */ char *strs[] = { "1: EE459Lx jl16-7.c - 5/17/12", "2: ABCDEFGHIJKLMNOPQRSTUVWXYZ", "3: JL16 test of 24LC256 EEPROM" }; unsigned char rdata[32]; // Buffer for reading data from EEPROM unsigned char ostr[80]; // Buffer for creating strings to go out SCI /* This pragma sets the area for allocating variables to the zero page of RAM (0x00 to 0xff) for more efficient access. */ #pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE unsigned char delay0; unsigned char delay1; /* This pragma sets the area for allocating variables back to the default area of RAM as defined in the PRM file. */ #pragma DATA_SEG DEFAULT void main(void) { unsigned char one = 1; unsigned char status; unsigned char n; char *sp; // EnableInterrupts; /* enable interrupts */ /* include your code here */ CONFIG1_COPD = 1; // Disable COP reset CONFIG2_IICSEL = 1; // I2C on PTA2,3 PTAPUE_PTAPUE1 = 1; // Enable pull-up for switch on PTA1 DDRA_DDRA1 = 0; // Switch input on PTA1; /* Set I2C rate to 38,400. Clock = 9.8304MHz. Divisor = 64. */ i2c_init((unsigned char) 3); // Initialize the I2C port /* Set SCI rate for 9600 baud. Assumes a 9.8304MHz clock */ sci_init(0, 2); // Initialize the SCI port sci_outs("\r\njl16-7.c\r\n"); // Read the data at the address. Stored string should be 31 characters or less. (void) memset(rdata, 0, 32); status = rdprom(rdata, 32, (word) DATA_ADDR); (void) sprintf(ostr, "Read \"%s\" from EEPROM %d\r\n", rdata, DATA_ADDR); sci_outs(ostr); (void) sprintf(ostr, "Status = 0x%2.2x\r\n", status); sci_outs(ostr); if (PTA_PTA1 == 0) { // If button down, write data // Figure out which string is there now and write a different one n = rdata[0] - '0' - 1; if (n < 3) { // Should be 0, 1 or 2 for the strings above n++; if (n >= 3) n = 0; sp = strs[n]; } else sp = strs[0]; // Write data at the address (void) sprintf(ostr, "Storing \"%s\" at EEPROM %d\r\n", sp, DATA_ADDR); sci_outs(ostr); n = strlen(sp) + 1; status = wrprom((unsigned char *) sp, n, (word) DATA_ADDR); (void) sprintf(ostr, "Status = 0x%2.2x\r\n", status); sci_outs(ostr); // Read back the data just written (void) memset(rdata, 0, 32); status = rdprom(rdata, 32, (word) DATA_ADDR); (void) sprintf(ostr, "Read \"%s\" from EEPROM %d\r\n", rdata, DATA_ADDR); sci_outs(ostr); (void) sprintf(ostr, "Status = 0x%2.2x\r\n", status); sci_outs(ostr); } else { (void) sprintf(ostr, "Bypassing storing data\r\n"); sci_outs(ostr); } while (one) { // Loop forever } } /* wrprom - write bytes to the EEPROM over the I2C bus */ unsigned char wrprom(unsigned char *p, int n, word a) { unsigned int maxw; // Maximum bytes to write in the page unsigned char status; unsigned char adata[2]; // Array to hold the address while (n > 0) { adata[0] = a >> 8; // Put EEPROM address in adata buffer, adata[1] = a & 0xff; // MSB first, LSB second // We can write up to the next 64 byte boundary, // but no more than is left to write maxw = 64 - (a % 64); // Max for this page if (n < maxw) maxw = n; // Number left to write in page status = i2c_io(EEPROM_ADDR, adata, 2, p, maxw, NULL, 0); if (status != 0) return(status); del1m(5); // Wait 5ms for EEPROM to write p += maxw; // Increment array address a += maxw; // Increment address n -= maxw; // Decrement byte count } return(0); } /* rdprom - read bytes from the EEPROM over the I2C bus */ unsigned char rdprom(unsigned char *p, int n, word a) { unsigned char status; unsigned char adata[2]; // Array to hold the address adata[0] = a >> 8; // Put EEPROM address in adata buffer, adata[1] = a & 0xff; // MSB first, LSB second status = i2c_io(EEPROM_ADDR, adata, 2, NULL, 0, p, n); return(status); } /* ----------------------------------------------------------------------- */ /* i2c_io - write and read bytes to a slave I2C device This funtions write "an" bytes from array "ap" and then "wn" bytes from array "wp" to I2C device at address "device_addr". It then reads "rn" bytes from the same device to array "rp". Returns: 0 if sucess 1 if failed, usually not getting an ACK after a write - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This "i2c_io" I2C routine is an attempt to provide an I/O function for both reading and writing, rather than have separate functions. I2C writes consist of sending a stream of bytes to the slave device. In some cases the first few bytes may be the internal address in the device, and then the data to be stored follows. For example, EEPROMs like the 24LC256 require a two-byte address to precede the data. The DS1307 RTC requires a one-byte address. I2C reads often consist of first writing one or two bytes of internal address data to the device and then reading back a stream of bytes starting from that address. Some devices appear to claim that that reads can be done without first doing the address writes, but so far I haven't been able to get any to work that way. This function does writing and reading by using pointers to three arrays "ap", "wp", and "rp". The function performs the following actions in this order: If "an" is greater than zero, then "an" bytes are written from array "ap" If "wn" is greater then zero, then "wn" bytes are written from array "wp" If "rn" is greater then zero, then "rn" byte are read into array "rp" Any of the "an", "wn", or "rn" can be zero. The reason for separate "ap" and "wp" arrays is that the address data can be taken from one array, and the the write data from another without requiring that the contents be merged into one array before calling the function. This means the following three calls all do exactly the same thing. i2c_io(0xA0, buf, 100, NULL, 0, NULL, 0); i2c_io(0xA0, NULL, 0, buf, 100, NULL, 0); 12c_io(0xA0, buf, 2, buf+2, 98, NULL, 0); In all cases 100 bytes from array "buf" will be written to the I2C device at bus address 0xA0. A typical write with a 2-byte address is done with i2c_io(0xA0, abuf, 2, wbuf, 50, NULL, 0); A typical read with a 1-byte address is done with i2c_io(0xD0, abuf, 1, NULL, 0, rbuf, 20); */ unsigned char i2c_io(unsigned char device_addr, unsigned char *ap, unsigned int an, unsigned char *wp, unsigned int wn, unsigned char *rp, unsigned int rn) { unsigned char ch, wrote, send_stop, status; ch = MMDRR; // This should clear the MMRXBF flag status = 0; wrote = 0; send_stop = 0; if (an > 0 || wn > 0) { wrote = 1; send_stop = 1; // Find the first byte to send in the ap or wp array if (an > 0) { ch = *ap++; an--; } else { ch = *wp++; wn--; } MMADR = device_addr; // Device address -> address reg. MMDTR = ch; // Put first byte in data register MIMCR_MMRW = 0; // Set for transmit MIMCR_MMAST = 1; // Initiate transfer while (an-- > 0) { while (!(MMSR_MMTXBE)); // Wait for TX buffer empty if (MMSR_MMRXAK) { // Check for NAK status = 1; goto nakstop; } MMDTR = *ap++; // Put next byte from ap array in data register } while (wn-- > 0) { while (!(MMSR_MMTXBE)); // Wait for TX buffer empty if (MMSR_MMRXAK) { // Check for NAK status = 1; goto nakstop; } MMDTR = *wp++; // Put next byte from wp array in data register } while (!(MMSR_MMTXBE)); // Wait for TX buffer empty of last byte if (MMSR_MMRXAK) { // Check for NAK of next to last byte status = 1; goto nakstop; } MMDTR = 0xff; // Dummy data to get ACK clock } if (rn > 0) { send_stop = 1; MMCR_MMTXAK = 0; // MCU will ACK received data MMADR = device_addr; // device address -> address reg. MIMCR_MMRW = 1; // Set for receive if (wrote) // See if we need to set for repeated start MMCR_REPSEN = 1; MIMCR_MMAST = 1; // Initiate transfer while (!(MMSR_MMTXBE)); // Wait for TX buffer empty if (MMSR_MMRXAK) { // Check for NAK status = 1; goto nakstop; } MMDTR = 0xff; // Dummy data -> data reg rn--; // Loop n-1 times before last read while (rn > 0) { while (!(MMSR_MMRXBF)); // Wait for RX buffer full *rp++ = MMDRR; // Get data rn--; // Decrement total count } MMCR_MMTXAK = 1; // Don't send ACK for last byte while (!(MMSR_MMRXBF)); // Wait for RX buffer full *rp++ = MMDRR; // Get data status = 0; } nakstop: // Come here to send STOP after a NAK if (send_stop) MIMCR_MMAST = 0; // Generate STOP bit return(status); } /* i2c_init - Initialize the MMIIC port */ void i2c_init(unsigned char bdiv) { MIMCR_MMBR = bdiv; // Set baud rate divisor MMCR_MMEN = 1; // Enable MMIIC } /* ----------------------------------------------------------------------- */ /* sci_init - Initialize the SCI port */ void sci_init(unsigned char scp, unsigned char scr) { SCC1_ENSCI = 1; // Enable SCI SCBR_SCP = scp; // Baud rate prescaler SCBR_SCR = scr; // Baud rate divisor SCC2_TE = 1; // Enable transmitter } /* sci_out - Output a byte to SCI port */ void sci_out(unsigned char ch) { while (SCS1_SCTE == 0); SCDR = ch; } /* sci_outs - Print the contents of the character string "s" out the SCI port. The string must be terminated by a zero byte. */ void sci_outs(unsigned char *s) { unsigned char ch; while ((ch = *s++) != (unsigned char) NULL) sci_out(ch); } /* ----------------------------------------------------------------------- */ /* del1m - Delay about 1msec times the "d" argument. */ void del1m(int d) { while (d--) { asm { ; The following code delays 1 millisecond by looping. ; Total time is 4 + 5 * (4 + 68 * (4 + 3) + 4 + 3) = 2439 cycles ; A 9.8304MHz external clock gives an internal CPU clock of ; 2.4576MHz (407ns/cycle). Delay is then .993 milliseconds. mov #$5,delay1 ; 4 cycles m1: mov #68,delay0 ; 4 cycles m0: dec delay0 ; 4 cycles bne m0 ; 3 cycles dec delay1 ; 4 cycles bne m1 ; 3 cycles } } }