Documente Academic
Documente Profesional
Documente Cultură
The two I2C peripheral modules of the PIC24Fj128GA010 are controlled by the
I2CxCON register that allows us to activate pretty much every function and
capability of the module:
In practice you can consider it split in two parts: the top bits (bit15-bit 5) of the
register define the configuration and mode of operation of the peripheral. The
bottom five bits (bit4-bit0) are triggers that initiate I2C protocol sequences. For
example, setting SEN initiates a START bit sequence. Setting PEN activates a
STOP bit sequence and so on
When a sequence terminates the corresponding bit in the control register is
cleared.
There is also a status register, I2CxSTAT, available to report when errors have
been encountered and to report a number of special conditions (mostly useful
when operating in slave mode).
Each I2C module has a dedicated Baud Rate register, I2CxBRG, essentially a
divider of the system clock, that allows us to control the speed of execution of
each sequence relieving us from the need to time each operation manually. Even
if in the following, for simplicity, we will wait for each sequence to complete, we
could devise the whole program so to use interrupts and avoiding any delay.
The MPLAB C30 compiler offers a set of basic libraries that support the most
common peripherals available on all devices of the PIC24 family. I have
purposely avoided using most of them in the book to illustrate direct hardware
control in C and, eventually, showing the readers how to create their own library
modules instead. In the following though, I will make use of the I2C.h peripheral
library as it provides just such a thin layer of abstraction to be useful without
getting in the way and allowing us to learn to use the hardware peripheral
quickly.
But in order to use any of the peripheral libraries with the MPLAB C30 compiler,
in addition to the usual linker script file, we need to include a specific library file
libpPIC24Fxxx-coff.a in the project list.
This is not an intuitive step and the right file is not exactly easy to find, unless
you know where to look for it. If you installed MPLAB in the default directory, the
path will be: c:\Program Files\Microchip\MPLAB C30\lib
Finally we get to create a new source file (well call it see24.c) starting with the
usual device include file and, now that we learned how to define the
configuration bits in the source, we will add those too before including the I2C.h
library file:
/*
** SEE24.c
** 24LCXX I2C serial EEPROM access demo
*/
#include <p24fj128ga010.h>
_CONFIG1( JTAGEN_OFF & GCP_OFF & GWRP_OFF & ICS_PGx2 & FWDTEN_OFF)
_CONFIG2( FNOSC_PRIPLL & FCKSM_CSDCMD & OSCIOFNC_OFF &
POSCMOD_HS)
#define FCY 16000000L
#include <i2c.h>
Following a pattern similar to the one used in many other exercises before, we
will define an initialization function initSEE() that will take care of configuring the
I2C1 module to operate as a (single) master at the relatively low speed of
100kHz.
IdleI2C1();
T1CON=0x8030;
TMR1=0;
while( TMR1< 100);
} //initSEE
As you can see all the heavy lifting is performed by two functions calls:
OpenI2C1() essentially writes to I2C1CON the required (basic) configuration
passed as the first parameter, and writes to the I2C1BRG the required clock
divider value calculated for the given system clock frequency and the desired
bus frequency, passed as a second parameter.
The other function offered by the I2C.h libary, IdleI2C1(), is quite a handy one,
and we will make heavy use of it in the following. It is like a catch all function
that makes sure every sequence the I2C peripheral module is working on is
completed. A short timed delay loop, using Timer1, completes the initialization
sequence and has been added there only to make sure the serial EEPROM device
has time to settle after a power up sequence before our PIC24 starts tormenting
it.
In the previous posting, we have seen the sequence required to issue a write
command to a serial EEPROM. Today we will extend that sequence to store a 16-
bit value (two bytes) using what is called a page write sequence. We can
expressed it using our compact notation:
The first part of this sequence (highlighted) is responsible for selecting a specific
memory location, the address, where the write operation will begin. It can be
conveniently encapsulated in a function on his own as it will return to be useful
later.
Here is how we can code it using two new functions: StartI2C1() and
MasterWriteI2C1() whose respective role will be obvious:
if (I2C1STATbits.ACKSTAT==0)
break;
StopI2C1();
IdleI2C1();
} // while waiting for ACK
It is only at this point that the addresSEE() function sends a second byte
containing the lsb of the address and returns.
The writeSEE() function can now use the addressSEE() function and then
complete the sequence passing the two bytes of data (the 16-bit value) that will
be written at the selected address and following location.
// 1. select address
cmd = addressSEE( add);
MasterWriteI2C1( v>>8);
IdleI2C1();
} //writeSEE
Notice that the serial EEPROM will begin the actual write operation only after the
Stop sequence is completed by the master. Different Serial EEPROM models
might accept a different number of bytes and require a specific address
alignment to perform the page write. The 24LC16 type memory, we assume will
be used in this example, can in fact accept as many as 16 bytes. It can store
them in an internal buffer and write them simultaneously (in a single write cycle)
upon receiving the Stop sequence.
The readSEE() function
If the simplest read sequence for a serial EEPROM is the current address read,
expressed in the previous posting in the compact notation:
Extending it to read sequentially two bytes (to obtain a 16-bit value) is simple
enough if after reading the first byte we respond to the serial EEPROM with an
Ack, inviting the device to continue streaming out the content of the next
memory location:
Since we have already created the addressSEE() function providing the first part,
completing the readSEE() function is a trivial exercise now:
// 1. select address
cmd = addressSEE( add);
StopI2C1();
IdleI2C1();
// 2. read command
StartI2C1(); IdleI2C1();
MasterWriteI2C1( cmd+READ_CMD);
IdleI2C1();
// 3. stream data in (will continue until NACK is sent)
r= MasterReadI2C1( );
AckI2C1(); IdleI2C1();
r|= (MasterReadI2C1()<<8);
return r;
} // readSEE
A simple for loop in the main() function of our project will test our ability to
write (and read back) to every and each location of the 24LC16 device.
main (void )
{ // test the serial EEPROM
initSEE( FCY);
// main loop
while( 1);
}
Lets mount an 8-pin socket in the prototyping area of the Explorer16 board and
you connect two pull up resistors to the 5V rail and respectively to the pins:
56 SDA1 RG3
57- SCL1 RG2
of the PIC24fj128GA010 and the SDA(5) and SCL(6) pins of a 24LC16B serial
EEPROM.
Also connect the WP(7) and Vcc pins to the 5V rail and the Vss(4) pin to GND.