For a project I'd like three PICs (two slaves PIC18F4620, one master PIC18F46K22) to communicate over the I2C bus. Later on, more slaves may be added (like EEPROM, SRAM, …). I'm writing the code for these PICs in C using the C18 compiler. I've looked around on the internet a lot, but couldn't find libraries to handle the (M)SSP peripheral. I've read the datasheet of both PICs on the (M)SSP peripheral in I2C mode but couldn't find out how to interface the bus.
So I need master and slave libraries.
What do you recommend? Do you have such a library somewhere? Is it built-in in the compiler and if yes, where? Is there a good tutorial somewhere on the net?
Best Answer
Microchip wrote application notes about this:
The application notes are working with ASM but that can be ported to C easily.
Microchip's free C18 and XC8 compilers have I2C functions. You can read more about them in the Compiler libraries documentation, section 2.4. Here's some quick start info:
Setting up
You already have Microchip's C18 or XC8 compiler. They both have built-in I2C functions. To use them, you need to include
i2c.h
:If you want to have a look at the source code, you can find it here:
installation_path
/v
x.xx
/h/i2c.h
installation_path
/v
x.xx
/src/pmc_common/i2c/
installation_path
/v
x.xx
/include/plib/i2c.h
installation_path
/v
x.xx
/sources/pic18/plib/i2c/
In the documentation, you can find in which file in the
/i2c/
folder a function is located.Opening the connection
If you're familiar with Microchip's MSSP modules, you'll know they first have to be initialized. You can open an I2C connection on a MSSP port using the
OpenI2C
function. This is how it's defined:With
sync_mode
, you can select if the device is master or slave,and, if it's a slave, whether it should use a 10-bit or 7-bit address. Most of the time, 7-bits is used, especially in small applications. The options forsync_mode
are:SLAVE_7
- Slave mode, 7-bit addressSLAVE_10
- Slave mode, 10-bit addressMASTER
- Master modeWith
slew
, you can select if the device should use slew rate. More about what it is here: What is slew rate for I2C?Two MSSP modules
There's something special about devices with two MSSP modules, like the PIC18F46K22. They have two sets of functions, one for module 1 and one for module 2. For example, instead of
OpenI2C()
, they haveOpenI2C1()
andopenI2C2()
.Okay, so you've set it all up and opened the connection. Now let's do some examples:
Examples
Master write example
If you're familiar with the I2C protocol, you'll know a typical master write sequence looks like this:
At first, we send a START condition. Consider this picking up the phone. Then, the address with a Write bit - dialing the number. At this point, the slave with the sent address knows he's being called. He sends an Acknowledgement ("Hello"). Now, the master device can go send data - he starts talking. He sends any amount of bytes. After each byte, the slave should ACK the received data ("yes, I hear you"). When the master device has finished talking, he hangs up with the STOP condition.
In C, the master write sequence would look like this for the master:
Master read example
The master read sequence is slightly different from the write sequence:
Again, the master initiates the call and dials the number. However, he now wants to get information. The slave first answers the call, then starts talking (sending data). The master acknowledges every byte until he has enough information. Then he sends a Not-ACK and hangs up with a STOP condition.
In C, this would look like this for the master part:
Slave code
For the slave, it's best to use an Interrupt Service Routine or ISR. You can setup your microcontroller to receive an interrupt when your address is called. That way you don't have to check the bus constantly.
First, let's set up the basics for the interrupts. You'll have to enable interrupts, and add an ISR. It's important that PIC18s have two levels of interrupts: high and low. We're going to set I2C as a high priority interrupt, because it's very important to reply to an I2C call. What we're going to do is the following:
GOTO
instruction to the general ISR on the high priority interrupt vector. We can't put the general ISR directly on the vector because it's too large in many cases.Here's a code example:
Next thing to do is to enable the high priority interrupt when the chip initializes. This can be done by some simple register manipulations:
Now, we have interrupts working. If you're implementing this, I'd check it now. Write a basic
SSPISR()
to start blinking an LED when an SSP interrupt occurs.Okay, so you got your interrupts working. Now let's write some real code for the
SSPISR()
function. But first some theory. We distinguish five different I2C interrupt types:You can check at what state you are by checking the bits in the
SSPSTAT
register. This register is as follows in I2C mode (unused or irrelevant bits are omitted):With this data, it's easy to see how to see what state the I2C module is in:
In software, it's best to use state 5 as the default, which is assumed when the requirements for the other states aren't met. That way, you don't reply when you don't know what's going on, because the slave doesn't respond to a NACK.
Anyway, let's have a look at the code:
You can see how you can check the
SSPSTAT
register (first ANDed with0x2d
so that we only have the useful bits) using bitmasks in order to see what interrupt type we have.It's your job to find out what you have to send or do when you respond to an interrupt: it depends on your application.
References
Again, I'd like to mention the application notes Microchip wrote about I2C:
There's documentation for the compiler libraries: Compiler libraries documentation
When setting up something yourself, check the datasheet of your chip on the (M)SSP section for I2C communication. I used the PIC18F46K22 for the master part and the PIC18F4620 for the slave part.