I2C Basic Functions
In this section, I will outline some of the basic functions for the I2C module on a PIC24. Since the bulk of the electrical functions were explained in section 1 of the tutorial, this section mainly focuses on the I2C module of the PIC24.
Word of Caution: Silicon Errata
There is a bug in the PIC24FJ64GA004 series of chips (16GA002, 32GA002, 48GA002, 64GA002, 16GA004, 32GA004, 48GA004, 64GA004). The bug is listed in the silicon errata section on Mircochip’s website. Go there and search for Silicon Errata, and select the chip you are using. If the chip you are using is not affected by the error then you are good to go. However, if the error is present you’ll need to try to get around the problem. In the PIC24F series of chips, there are two I2C modules. The I2C1 module does NOT work correctly in the master mode. You can read more about it in my post on Microchip’s developer forums.
There is a work around to making the I2C1 module work, but it is clumsy and does not always guarantee success. For this reason, if you are thinking about using the I2C module, I would suggest using only the I2C2 module on the PIC24F64 family, or just avoid the whole PIC24FJ64 family all together. I much prefer the PIC24H family anyways.
The Registers
As always, the starting place to understanding how to a module is with the registers. In this case, there are mainly 3 registers of great concern.

The first is I2CxCON. This register is the main control register. With it, you can initiate start bit, stop bits, and test to see if acknowledge bits were received. The next one is the I2CxSTAT. This register allows you to see what the current status of the I2C module. This may include polling to see if there are errors, bit collisions, receive buffer overflows and other occurrences. Lastly there is the I2CxBRG, which determines the bit rate at which the module operates.
There are other minor registers such as I2CxRCV, I2CxTRN and I2CxMSK. The I2CxRCV register is used to retrieve received data. The I2CxTRN register is used to send data. The I2CxMSK is not used in our example since it is only used when the module is set to Slave mode.
When using the I2CxCON register, you must be careful with some of the initiation bits. When setting a Start or Stop bit, the hardware will automatically clear the set bit when it is ready to proceed with processing data. This means that it is not enough to just initiate a start bit and then proceed to send or receive data. You must keep polling that bit until the PIC automatically clears the bit, and only then proceed with processing data. There will be more on this matter later in the basic functions.
Using the I2C Module
The way I approach the I2C module is analogous to making sentences. I start with letters, which adds up to words, and combine them to make sentences. The individual base commands and the fiddling with the registers are kind of like the “letters”. I use these commands to form basic functions, functions that write or read one byte (words), and finally I combine these functions into full packets, with address, sub-address, and data (full sentences).
Recall that a basic “write” packet is composed of a chip (or slave) address, a sub-address, and the data. A “read” packet is composed of a chip address and a sub-address in the write configuration, followed by a chip address and the data sent by the slave chip.
Each of the individual bits and bytes must be sent is a specific order. The whole packet must be sent correctly for the slave to understand the intention of the master.
There are quite a few functions in this tutorial. I don’t think I need to go through every one of them, but I will annotate here and there if things are not very clear.
Initiation
Several things must be initiated before using the module. I use the following function.
“void i2c_init(int BRG)”
//function initiates I2C1 module to baud rate BRG
void i2c_init(int BRG)
{
int temp;
// I2CBRG = 194 for 10Mhz OSCI with PPL with 100kHz I2C clock
I2C1BRG = BRG;
I2C1CONbits.I2CEN = 0; // Disable I2C Mode
I2C1CONbits.DISSLW = 1; // Disable slew rate control
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2C1CONbits.I2CEN = 1; // Enable I2C Mode
temp = I2CRCV; // read buffer to clear buffer full
reset_i2c_bus(); // set bus to idle
}
The “reset_i2c_bus()” function is a basic function that set the I2C bus to an idle state. This way the bus is ready to be used immediately after initiation.
Usually, slave devices are rated for up to 100 kHz. The “fast” specifications require that devices function up to 400 kHz. In the newest spec for the I2C bus, the “ultra fast” allows devices to function up to 1 Mhz, which is really fast. I rarely need anything that fast, and generally just stick to something slow like 40 kHz or so. It doesn’t really matter what baud rate you pick since the controller is sending the clock signal anyways. With a 10 MHz oscillator, I usually set the BRG value to about 100. This works for most applications.
Basic Functions
In order to initiate a start bit or a stop bit, there are some intricacies that a user must be aware. First, as I mentioned earlier, the hardware automatically clears certain bits in the I2CxCON register. Again, I cannot emphasize how important it is to read and UNDERSTAND the datasheet. Because of the way these bits work, when I want to initiate a start or stop condition, I must write a loop that keeps on polling the bit until the hardware clear, after which I can proceeding to do my sending and receiving. Another way to do this would be to put a long delay, long enough that assure the occurrence of the hardware clear. I usually take the former approach, as it requires more coding, but is more efficient. The following is how I write the function to initiate a start bit and restart bit:
“void i2c_start(void)”
//function iniates a start condition on bus
void i2c_start(void)
{
int x = 0;
I2C1CONbits.ACKDT = 0; //Reset any previous Ack
DelayuSec(10);
I2C1CONbits.SEN = 1; //Initiate Start condition
Nop();
//the hardware will automatically clear Start Bit
//wait for automatic clear before proceding
while (I2C1CONbits.SEN)
{
DelayuSec(1);
x++;
if (x > 20)
break;
}
DelayuSec(2);
}
“void i2c_restart(void)”
//Resets the I2C bus to Idle
void reset_i2c_bus(void)
{
int x = 0;
//initiate stop bit
I2C1CONbits.PEN = 1;
//wait for hardware clear of stop bit
while (I2C1CONbits.PEN)
{
DelayuSec(1);
x ++;
if (x > 20) break;
}
I2C1CONbits.RCEN = 0;
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2C1STATbits.IWCOL = 0;
I2C1STATbits.BCL = 0;
DelayuSec(10);
}
I also need a function to initiate a stop bit. However, I realize that when I initiate a stop bit, what I really want to do is put the I2C bus in an idle state. For this reason, I reset a whole bunch of registers every time I initiate a stop bit to clear them of any previous errors and ACKs. Again beware of automatic hardware clears as in the case of I2C1CONbits.PEN.
“void reset_i2c_bus(void)”
//basic I2C byte send
char send_i2c_byte(int data)
{
int i;
while (I2C1STATbits.TBF) { }
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2CTRN = data; // load the outgoing data byte
// wait for transmission
for (i=0; i<500; i++)
{
if (!I2C1STATbits.TRSTAT) break;
DelayuSec(1);
}
if (i == 500) {
return(1);
}
// Check for NO_ACK from slave, abort if not found
if (I2C1STATbits.ACKSTAT == 1)
{
reset_i2c_bus();
return(1);
}
DelayuSec(2);
return(0);
}
Send and Receive, Write and Read
The basic “send” function is written as follows:
“char send_i2c_byte(int data)”
//basic I2C byte send
char send_i2c_byte(int data)
{
int i;
while (I2C1STATbits.TBF) { }
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2CTRN = data; // load the outgoing data byte
// wait for transmission
for (i=0; i<500; i++)
{
if (!I2C1STATbits.TRSTAT) break;
DelayuSec(1);
}
if (i == 500) {
return(1);
}
// Check for NO_ACK from slave, abort if not found
if (I2C1STATbits.ACKSTAT == 1)
{
reset_i2c_bus();
return(1);
}
DelayuSec(2);
return(0);
}
The return of the function sends back a 1 if there is an error, and a 0 if the transmission went through correctly.
I use two different read functions. This is because a sequential read requires the use of an ACK sent by the master to initiate the next byte. A random read of a single byte of data does not require an ACK to be sent.
“char i2c_read(void)”
//function reads data, returns the read data, no ack
char i2c_read(void)
{
int i = 0;
char data = 0;
//set I2C module to receive
I2C1CONbits.RCEN = 1;
//if no response, break
while (!I2C1STATbits.RBF)
{
i ++;
if (i > 2000) break;
}
//get data from I2CRCV register
data = I2CRCV;
//return data
return data;
}
“char i2c_read_ack(void)”
//function reads data, returns the read data, with ack
char i2c_read_ack(void) //does not reset bus!!!
{
int i = 0;
char data = 0;
//set I2C module to receive
I2C1CONbits.RCEN = 1;
//if no response, break
while (!I2C1STATbits.RBF)
{
i++;
if (i > 2000) break;
}
//get data from I2CRCV register
data = I2CRCV;
//set ACK to high
I2C1CONbits.ACKEN = 1;
//wait before exiting
DelayuSec(10);
//return data
return data;
}
Putting it Together
There you have it. Those are the basic “words” in a sentence. To use my basic I2C functions, I need to follow the I2C standard.
The I2C standard requires that a random write be sent in the following manner:

The PIC (master) must send a start bit, followed by a device address, with the WRITE command. The slave then sends an ACK to acknowledge the byte. Next the master sends the sub-address, followed by a slave ACK. Lastly it sends the data, followed by a slave ACK, and finishes with a stop bit.
If you look closely at the “send_i2c_byte(int data)” function, you’ll see that it waits for an ACK from the slave. If a /ACK is detected (no acknowledge detected), then it returns an error and sets the I2C bus back to an IDLE state. This is exactly what we need.
A random write function is composed of “words” of basic functions in the exact manner required by the I2C specifications:
“void I2Cwrite(char addr, char subaddr, char value)”
void I2Cwrite(char addr, char subaddr, char value)
{
i2c_start();
send_i2c_byte(addr);
send_i2c_byte(subaddr);
send_i2c_byte(value);
reset_i2c_bus();
}
In the same way, a random read must be sent in the following manner:

The random read function starts off exactly the same way as a random write. However, after the 2nd byte, the master must initiate a RESTART bit, followed by the device address with a read command. The slave sends and ACK, followed by the data requested by the master. The master then sends a /ACK, before issuing a stop bit. I wrote the following function to emulate the I2C requirements:
“char I2Cread(char addr, char subaddr)
char I2Cread(char addr, char subaddr)
{
char temp;
i2c_start();
send_i2c_byte(addr);
send_i2c_byte(subaddr);
DelayuSec(10);
i2c_restart();
send_i2c_byte(addr | 0x01);
temp = i2c_read();
reset_i2c_bus();
return temp;
}
The function returns the data received.
Polling
Polling is an important function during I2C operations. I usually use a poll function before any read or writes because I want to make sure that the device I think is on my I2C bus is actually on my I2C bus. I eliminates communication errors that otherwise might escape the regular functions’ error handling abilities. The premise of the polling function is to wait for the ACK from a slave device. If a slave device sends back an ACK, it means that it is on the I2C bus and functioning properly.
“unsigned char I2Cpoll(char addr)”
unsigned char I2Cpoll(char addr)
{
unsigned char temp = 0;
i2c_start();
temp = send_i2c_byte(addr);
reset_i2c_bus();
return temp;
}
There you have it. That’s how I2C works on an PIC24 I2C module. The last section of the I2C tutorial will deal with usage, examples, and advanced functions.
I borrowed your I2C code for my project and it worked on the very first try. Thank you for saving me some time here.
Do you happen to have an example for SPI communication? My write process is working great but I cannot get my reads to work.
Hi Ryan, unfortunately my current list of projects does not deal with SPI, I don’t have any code as of right now. The good news is that C30 and C18 comes with a lot of the peripheral code in the “src” folder. I should be in the folder in which you installed C30. The read/write commands are probably already there. If you’re still stuck, leave me a comment and I can take a look at what’s the problem.
-J
HI,
Im using PIC24FJ64GA002 for my i2c slave.
Now, i need to use both i2c module since i’m required to have
a redundancy feature on my design.
Problem is, i already have a code that works perfectly on I2C2
module but when i use the code on I2C1 module [Registers configured
to I2C1] it will not work.
Did you experience this error before? I’m really stuck on this..
Thanks.
Yes. Check the FJ64GA002 Silicon Errata. The I2C1 module has a silicon-level bug. I2C2 works just fine, but the work around for the I2C1 module is posted here (I actually posted the thread on MC’s site):
http://www.microchip.com/forums/tm.aspx?m=271183&mpage=1&key=񂍏
I think there is a new version of the errata that is out. You might need to verify your silicon level to make sure which workaround is needed.
-J
“The bug is listed in the silicon errata section on —>Mircochip’s website”
Little typo
Wait… where’s the typo?
-J
The Silicon Errata Rev B4 no longer lists the Master Mode bus collision and the Slave Mode ACK problems that were listed in SIlicon Errata A3. Does that mean we can safely assume those problems have been corrected? Anyone verified this? There are new problems having to do with 10-bit addressing but thankfully that doesn’t affect me
There is a register (check the datasheet), that lists your silicon revision. If you got your chip from Digikey or any distributors, they might have been bought and manufactured for a while. You don’t really know how long they have been in stock. The only way to know is to check that register.
-J
Thanks, J, that’s good to know. My question, however, wasn’t really about knowing the revision level of a particular part, but to make sure that the B4 errata supercedes the A3 errata and is not simply appended to it. In other words, did Microchip fix the A3 problems?
Yes, a subsequent revision should correct all flaws in the errata. However, fixing the old bugs might introduce new bugs. In that sense PCB layout guys are very much like with VLSI (silicon level layout) people: they are both very superstitious. If something works, don’t change it. There are other reason however, for taking a long time before fixing a problem. If there is a workaround for the problem, and the part is, in general, very usable, then manufacturers are reluctant to fix it. This is because creating the lithography mask for the silicon die is very expensive. Usually, as a manufacturer, you want the product to be released for a decent amount of time for all the silicon level bugs to be found, and then do a spin that corrects all the bugs in one retooling of the masks.
-J
I believe your “char i2c_read(void)” read routine has an error in it. You still have to do an acknowledge sequence by sending out a NACK. The ACKDT bit in I2CICON should be set to ’1′ and then ACKEN should be set to initiate the nack event at the end of the read.
True.. you can do that, but since it’s being pulled up with the pull-up resistor, it’ll be and NACK anyways. The result is a logic high on the SDA line whether you put that line or not.
-J
But, you are not generating the 9th clock pulse necessary to recognize the NACK if you do it that way.
Nope. Check the scope trace, the 9th pulse is generated regardless of whether you send an ACK or NACK or send anything at all. The module is designed this way because many IC’s will go into an infinite loop waiting for the 9th clock pulse, so the 9th clock is guaranteed.
-J
Thanks for your work!
I’m trying to interface a PIC24HJ256GP206 (as master) with an Analog Device AD5934 (as slave), that is an impdence converter. For this use I use your I2C code with Microchip C30 Compiler, making some arrangements in writing function due to my particular slave device. The problem is that… nothing works. It’s the first time I use I2C and I expect to see on SCL the clock but if I put a scopemeter on SCL or SDA pins i find a 3.3Vdc constant voltage and nothing more (system Vcc is 3.3Vdc and PIC crystal clock is 10MHz with internal PLL). I start using a 4.7KOhm pull-up resistror, but now I’m trying a 1KOhm without any change. Possible? Someone has an idea of what is wrong? Thanks!
Well your pull up resistors are not your problem. Anything in the 7 to 2 KOhm range will work. First of all, I assume that when you say “scopemeter” you mean the oscilloscope. I am also assuming that you know how to use an oscilloscope properly. By no means do I mean the previous sentence in an offensive manner. It’s just that you are trying to catch digital signal that occurs within several microseconds, so proper trigger settings and time division is absolutely critical in order to see something on the scope.
You need to first make sure that the microcontroller is working correctly. You can test this by putting in a delay function and turning the IO’s up and down, OR you can set the OSCO pin up so that it outputs Fcy (you should see an oscillator output on OSCO). These tests ensure that your microcontroller circuit is working. Next you need to test your I2C bus. The easiest way to see if your I2C bus is configured properly is to send a start command followed by a delay, followed by a stop. You should see the SDA line go to a logic low. That said, I prefer to trigger on the SDA line rather than the SCL line. This is because when a start bit is initiated, the SDA line gets lowered first, followed by the clock. If you see a start and stop bit, then you should be able to debug the rest.
-J
Hi,
I am programming for I2C communication on PIC24FJ192GA010. I have written code for I2C, but when I try to write control byte into I2C2TRN buffer, the ACKSTAT bit in I2C2STAT register gets set (indicating NACK). Does this indicate that the slave is not sending acknowledgment to MASTER (MASTER not able to communicate with SLAVE?)?
Thanks,
Vishal
Ok, I found that I had to change my buad rate setting value for the problem.
Thanks,
Vishal
NACK means a logic high, so no slave devices is pulling SDA to logic low (GND). Either you are sending the byte incorrectly, or your slave devices are not functioning correctly.
-J
Thanks for a great site and tutorial. I have spent many days attempting to get I2C1 working as a Master on a PIC24 HJ 64GP202 series chip without success. No matter what I do, or what value pu resistor I choose, I cannot get an ACK from my slave when I write to the slave device from the PIC I can see on my digital scope that the SCL clock bursts are being generated at the correct pulse width (10us), and the SDA data is correct, but my slave never pulls down the SDA line with an ACK.
I have a little board that generates I2C data and is controlled by a PC over USB. It has a simple GUI that allows one to send and receive I2C from the PC’s keyboard. When I substitute this card’s SCL and SDA lines for the ones from the PIC, going to the same slave device on the same breadboard and send the same data to the slave, the waveforms look the same, except fnow the slave’s ACK pulses are there, and indeed the slave now executes the proper command that I have sent to it.
Any ideas would be greatly appreciated. Thank you.
Hi,
My instinct tells me that your slave/PIC is probably not powered/connected correctly. If you are able to talk to it using the PC, then the resistors are probably correct. Also check the I2CxSTAT to see if there is a collision. That might give you some clues. Out of curiosity, what is the slave device?
-J
Thanks for getting back to me…didn’t see your reply untiil just now. The slave device is a CIrrus Logic 8 channel audio volume control CS3318…. I got busy and haven’t had much time to continue working on this code – also I was so exasperated from continual failure over many days that I needed a break.
I am just getting back to this. I will report back when/if I get any further. Thanks
Hey, I had a question about the code posted. It makes use of the DelayuSec() function, but where is this function found?
Look up the Timer section.
-J
By far the most concise and up to date information I found on this topic. Sure glad that I navigated to your page by accident. I’ll be subscribing to your feed so that I can get the latest updates. Appreciate all the information here
JLiu,
I’m using I2C to interface with an EEPROM chip and when I have the PLL enabled on the microcontroller PIC24FJ64GA104, the I2C does not work. I have verified this by keeping the I2C clock at 100 KHz with and without the PLL option. The PLL option does not work. Any ideas why this would be ?
Thanks for the wonderful tutorial!
MT
On the PIC24 devices, there is a register that controls the peripheral clock. There are limits to how fast this clock can be, and with the PLL enabled, the speed might be too fast. Check the OSCILLATOR section of the datasheets and try to find this register (aptly named something like, “Peripheral clock divide”). See if this works. Also check your OSCO pin, and see if you are actually getting a clock out (which means that your PLL is actually locked and your processor is correctly clocked). Always perform this check because there are jitter and timing specs for the PLL, so you might think the PLL is locked when in fact it might not be.
-J
hi
i am trying to interface PIc24fj128ga010 on explorer 16 board with HMC6352 digital compass through I2C2 of pic. my system clock is 4MHz and I am generating 100KHz SCL for I2C .
This is my sequence.
start bit
address with write bit
command to initialize HMC module
stop bit
start bit
addr+write
command to read heading data from HMC
stop
start
address+read bit
reads from HMC
stop.
I am making SDA idle whenever necessary. everything goes fine until i send the address of HMC6352
with read bit. After sending that SCL and SDA signals all disappear and the lines goes to low state.
what could be wrong ? I am struggling for past 1 week.
Please help me.
Please post some code and scope traces. Probably a loop problem.
-J
sorry i have seen your post now only. my problem is solved when a delay is added after the read command.
Thanks for your code. The functions required for writing seem to be working great. However, the code for the i2c_restart function appears to be missing from you post above. (There is a duplicate send_i2c_byte function.) If you could please email me the code or fix the post that would be very helpful.
Create a function similar to start, but using the IC2xCON.RSEN bit. You’ll have to fool around with it to figure it out. I’ll use a classic line in programming books: “I’ll leave this as an exercise for the reader to complete.”
-J
i have a problem concerning i2c_write. looks ok on i2c_init, but SDA1 and SCL1 is always pulled up. could you post how you used your code on an actual project?
im also using a 10k pull-up resistor.
im using pic24fj64ga002 mcu
for now im not using any slave device
i mean SDA1 and SCL1 is driven low. transmissions happen and ACK is receive.
Okay.. I’m very confused about your problem. Is it always pulled up or down? What is the set up etc. Did you initiate the I2C Module? Did you take a look at the example codes?
http://www.engscope.com/pic-example-codes/uart-based-i2c-controller/
-J
Im using I2C2 now. SDA is always pulled-up. can you send the codes for I2C2. i might miss something a register or something.
no slave device for now.
You make an tutorial very helpful.
But I have a problem with the 8 low bits on the register I2C1CON, I can’t set the SEN bits for start sequence, I have tried to enable I2CEN before I set SEN, but nothing has been changed. Neither can I send the address of LCD(slave) to I2CADD.
I use an PIC24F16KA102 to drive an LCD, but after 2 weeks’ works, the screen stays blank.
Can you help me please?
chen
@chen. If you put a SEN bit, the I2C1STAT register will change. This is useful for debugging. If there a collision condition, check your I2C1STAT register. It will tell you what error you should be looking for. For example if the device is not responsive, the ACK poll should return negative (which will be indicated on the I2C1STAT). In addition, master collisions, as well as write collisions will also be indicated. My guess is that you device is not working because there is a collision (due to the LCD being dead or otherwise). I cannot not help further if you don’t show code/describe more symptoms.
Best of luck,
-J
OK, I’m using a LCD which reference is BTHQ 21605V-FRE-I2C-COG, there are 5 pins: VLCD, VSS, VDD, SDA, POR, SCL. On the datasheet, it indicate the VLCD as “LCD driver voltage” and I don’t know why it is an output, several days ago I made a mistake, I supply it with 4.5v, maybe that why it doesn’t work? For the POR, I combine it with an RB14 on the chip as an reset for begin.
As for the code, I sent an reset(POR) with an logic 1 as begin, after 5ms delay I reset it and I waite 5s.
Then I start the fonction “i2c_start()” just like yours, but since the I2Cmodule is not enable I add “I2C1CONbits.I2CEN = 1;” at the beginning,
now only the 8 low bits on the registre I2CTRN and the
“I2CEN”(I2C enable bit);
“U-0″(unimplemented bit set to 1 just after my commande “reset = 1 ” stays 1 after”reset = 0″);
“SCLREL”(it was already 1 when I start, means releases SCL1 clock(when operating as I2C slave))
are set to 1, others are 0.
Then I sent the “SEN” bit for start condition, the bus collision bit on the I2C1STAT registre appeared.
Now I can say the LCD is dead? I will try to branch a new one.
The problem is solved……I add two 4.7k external resistances on the I2C bus, now I can commande SEN and PEN(I was confused by the datasheet of LCD, I thought they were integrated in the module), no more bus collision, the addresse is correctely send to the registre I2C1TRN, the ack is detected too. But the I2C1ADD, which should contain(isn’t it?) the addresse of slave does not changed, shall I may a fonction for it ? My sreen stays blank…after a I2CWRITE() fonction came from an initialised example for my LCD drive…
I have tested the codes and observed with a oscilloscope, the start sequence and the 8 bits addresse of slave + 1 bit W were successfully sent. But there is no ACK detected, SDA is not pulled down by the slave, so I can say the LCD is dead.
In fact, I always have a problem about the controle of TRISB.
The code below goes well:
TRISB = 0;
_RB15 = 1;
but not:
_TRISB15 = 0;
_RB15 = 1;
The configuration on the registre TRISB does not fonction individually.
How can I fix this problem? Thank you.
@ chen, you can definitely access the TRISB pins individually. There are bit fields already defined in the header file specifically for bits.
for PIC24F16KA102, open the file pic24f16ka102.h, and you should be able to find it.
In any case, the struct you need to access is called TRIBbits.TRISBx, so for your code, it would be something like
TRISBbits.TRISB15 = 0
This will cause trisb15 to be an output, while not disturbing trisb0 to trisb14.
-J
Yes~ in fact, it is defined in the header file that _TRISB15 = TRISBbits.TRISB15, _RB15 = PORTBbits.RB15. I use the most simple for test, sorry for hadn’t precise that before.
I have tried another time, the PROTB15 goes well (it’s strangelly set to 1 since the beginning) , but not for the PORTB from pin0 to pin3 and pin12 to pin 14 for my chip. Now I know they are all analog input bits, I have to configure all of them to digital mode!
Thanks for your wonderful tutorial! It is indeed the only PIC24 tutorial that I can found on the Internet, you have tell the importance of using bit control in PIC24 which helps me out. Now my LCD is displaying something, I found the RESET in the beginning of lcd_init is very important.
Thans anyway! If you don’t mind, may post more message after when I have some problem? I found that I have write a few useless message above…
@chen. please only post what is relevant to the website. I’d happy to answer them.
-J
Hi,
I am trying to connect my PIC24FJ256GA110 to the gyro ITG-3200 using I2C_3. I got the gyro functioning correctly using an I2C analyzer but I cannot do it using the PIC. Right now, I see in the oscope that the signal is going through with the right address but the gyro is not ACKing. (I see the same signal when I use the analyzer)
To start, I noticed in the code that you posted, that you never set up any of the pins. In order for me to see the SDA and SCL coming out I need to write:
PORTEbits.RE6 = 0×0
PORTEbits.RE7 = 0×0
TRISEbits.TRISE6 = 0×1
TRISEbits.TRISE7 = 0×1
If I comment out the last two lines of code, SCL and SDA stay HI. Where do you set up the pins in this tutorial? If you don’t need to do it, why? Is there something else that you think I should be setting up?
Thanks for the help,
balvi
@balvi. TRISxbits.TRISxx = 1 sets the tristates to input, meaning you are not using that pin as an output. This is the default behavior. Setting PORTxbits.Rxx to zero really doesn’t do anything. You should be accessing the LATxbits registers for output writes. The PORTxbits should only be used for reads. The peripheral modules actually overrides the TRIS and POR settings, so there’s no need to set them up. When the I2C ports are opened, the modules take over and while you may be able to access PORTx and TRISx, the behavior is undefined. Please refer to the IO portion of the datasheet to have a better understanding of how the TRISx, LATx and PORTx registers work. You can also read it on the IO portion of the tutorial.
-J
Hi J,
Thanks for the answer. I got the gyro to talk to PIC now but I still have a question. The gyro works at 3.3V so I thought I would need a voltage shifter from 5V to 3.3V. I used it in the connection btw the PIC and gyro and it worked. Then, just for curiosity I decided to connect SDA and SCL from the PIC to the gyro directly. (using pull-ups to 3.3V) and to my surprise it worked too. Do you know why this configuration still works? Is it because the PIC recognize 3.3V as a logic HI? Is it safe to leave it that way?
Thanks,
balvi
@balvi. I’m guessing you meant to say that the gyro works at 5V (not 3.3V as you stated). The DC specifications for the PIC24 is 5.5V for digital pins (you can view this data in the datasheet), which means that you can pull up to up to 5.5V and still not damage the pins. As an example, for part PIC24FJ256GB110, refer to P.318 of the datasheet on table 29-7. It specifies the max and min voltages that can be applied to the pins. For future reference, all voltage level and AC specification types of questions can be answered in the datasheets. The gyro you are using will also have a DC specification, which should be referred to. As always, learn to use the datasheets; they are printed for a reason.
Your question has to do with transistor levels, and it really depends on the process by which the silicon is created. Refer to http://www.interfacebus.com/voltage_LV_threshold.html for a list of LVTTL levels for common silicon processes. Because most I2C modules have to work at multiple voltage levels, they will likely use an LVTTL buffer to allow for more acceptable voltage range. You can see that the LVCMOS does not accept certain voltage levels. If a voltage between the allowable ranges are found, behavior is undefined and unpredictable.
-J
Hi, I am interfacing a 4×4 matrix keypad with GPIO expander MCP23008 as in Microchip AN1081. My controller is PIC24FJ64GA004 which communicates with the MCP23008 via I2C. My keypad works fine, except that after I leave it idle (i.e. keys not pressed) for a variable period of time, it stops responding. When this happens, the SDA and SDL lines stay high when a key is pressed and the INT signal doesn’t go low to generate the interrupt for the microcontroller. I am using 8 MHz oscillator, PLL and I2C2BRG is set to 157 (9D hex). My pull-up resistors on the I2C lines are 2.2K. Do you have any idea why this is happening? Thanks in advance.
@Ridha. The I2C on the earlier PIC24s tend to be a bit buggier than the later revisions. I would suggest completely turning the I2C modules off (ie. I2CxCONbits.I2CEN = 0;) when they are not used, and when an interrupt is detected, turn them back on. This way whatever glitch is occurring during idle time will be avoided. I have used this workaround before, but I don’t know if this will work in your case.
-J