Strict Standards: Declaration of action_plugin_googleanalytics::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /home/ulcape/www/wiki.ulcape.org/lib/plugins/googleanalytics/action.php on line 6

Strict Standards: Declaration of action_plugin_stripslashes::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /home/ulcape/www/wiki.ulcape.org/lib/plugins/stripslashes/action.php on line 0
CAPE Wiki » tutorials:pic:pic18_i2c
 

I2C

The 18F452 and the 18F4520 have a built-in I2C (Inter-Integrated Circuit) bus. I2C is a 2-wire synchronous serial bus. Some devices, such as sensors, communicate with microcontrollers on this 2-wire serial bus. Multiple devices, even different types of devices, can be attached to the same 2-wire I2C bus without causing collisions or errors. Each device contains a unique address allowing multiple devices attached to the same I2C bus. In our example, we will use a digital temperature sensor that communicates over I2C.

The following is geared for the 18F4520. This tutorial will also work with the 18F452 and the 18F4525. Before continuing, be sure to read through the first tutorial in this series, PIC Programming Basics

Getting Started

You will need the following:

  • a MAX6633 digital temperature sensor (mounted on a test board with wire leads)
  • two 1K to 2.2K resistors

The MAX6633 digital temperature sensor is a surface mount device. Unlike the other ICs, this one cannot be forced on a breadboard. It requires having a PC board made in which the sensor is mounted (soldered) and connected to leads or wires in order to test.

According to the digital temperature sensor datasheet, the sensor contains 8 pins:

  • power
  • ground
  • serial clock
  • serial data
  • 4 address pins

Wire the I2C Bus

  • On the PIC, connect the SCK pin (pin 18) to the MAX6633 SCL (pin 2).
  • On the PIC, connect the SDA pin (pin 23) to the MAX6633 SDA (pin 1).
  • Connect a resistor from the clock line to the digital power.
  • Connect a resistor from the data line to the digital power.
  • Connect all 4 address pins to digital ground.

In order to get this to work, I assume you are using an external crystal of 10MHz. If not, some adjustments will need to be made which I will address in the document. Also, you must have the latest version of MPLAB installed and the C18 compiler installed.

Open PORTC

According to the PIC18F4520 datasheet, you will notice that on the pinout, the I2C lines are located on pins 18 and 23 (CLK and DATA).

These pins happen to be located on PORT C. PORT C pins can be used for general input/output or can be configured for special uses such as I2C communication. Since I2C communication involves only 2 wires, these pins will serve as the I2C communication between the PIC and other I2C devices. The PIC will act as the “master” and all the other devices will act as the “slaves”.

First step is to “turn on” PORT C.

int main()
{
    TRISC = 0x00; //turn on tri-state register and
    //make all output pins
    PORTC = 0x00; //make all output pins LOW
}

Now you are ready to configure the I2C bus on the PIC.

I2C.H

In MPLAB, it is critical that you include the “i2c.h” file in your code. Therefore, be sure to add it at the top of the main file.

#include <i2c.h>
int main()
{
    TRISC = 0x00; //turn on tri-state register and
    //make all output pins
    PORTC = 0x00; //make all output pins LOW
}

You have now opened port C for use. Now, on to opening the I2C bus.

Configure OpenI2C()

Now it’s time to open and configure the I2C port on the PIC. According to the C18 Libraries file, OpenI2C is the function to call. A few things are needed to be understood. The first thing is the difference between MASTER and SLAVE. Since the PIC will control all the devices, the PIC should be set as MASTER. Since bus speed isn’t a concern, slew control can be turned off using SLEW_OFF parameter.

#include <i2c.h>
int main()
{
    TRISC = 0x00; //turn on tri-state register and
                  //make all output pins
    PORTC = 0x00; //make all output pins LOW
    OpenI2C( MASTER, SLEW_OFF);
}

The second thing is to understand what the SSPADD register does inside the PIC. It contains the value which represents what baud rate (speed) communications will occur based on A) what crystal you are using and B) what speed you would like. For our example, we will assume a crystal speed of 10MHz and baud rate of 100KHz. See page 153 of 332 of the PIC datasheet for all values listed.

#include <i2c.h>
int main()
{
    TRISC = 0x00; //turn on tri-state register and
                  //make all output pins
    PORTC = 0x00; //make all output pins LOW
 
    OpenI2C( MASTER, SLEW_OFF);
    SSPADD = 0x3F;
}

At this stage you have successfully initialized the I2C.

MAX6633

The MAX6633 temperature sensor communicates using I2C as a “slave”. As you can see, there are 2 pins for this, pins 1 and 2. Pin 1 is the DATA line. Pin 2 is the CLOCK line.

MAX6633 Schematic

According to the I2C specifications, both lines must be pulled HIGH using a resistor. In this example, a 1K resistor is used for both lines. I’ve used 2.2K and worked fine.

On page 6 of 16 of the MAX6633 datasheet, a table shows the timing pattern used in order to communicate with the device. We will use the functions located in the C18 libraries file which is apart of the ‘i2c.h’ library.

On page 7 of 16 of the MAX6633 datasheet, they show both registers inside the IC. CONFIGURATION and TEMPERATURE.

max6633registers.jpg

We will set the CONFIGURATION register first.

CONFIGURATION Register

Let’s add the code to set the configuration register. We will begin an I2C sequence.

#include <i2c.h>
int main()
{
    TRISC = 0x00;      // turn on tri-state register and
    // make all output pins
    PORTC = 0x00;      // make all output pins LOW</code>
 
    OpenI2C( MASTER, SLEW_OFF);
    SSPADD = 0x3F;
 
    StartI2C(); // begin I2C communication
        IdleI2C();
    WriteI2C( 0x80 );  // sends address to the
                       // device
        IdleI2C();
}

Let’s stop here and understand what’s going on. First thing we do is begin the communication by using StartI2C().

IdleI2C() is placed in between as protection to make sure that the PIC doesn’t get ahead of itself in code before the devices are ready. Either way, it’s a safe guard and it doesn’t hurt.

WriteI2C() is our first attempt to address an IC located on the I2C bus. Since many devices can be attached to this bus, this address ‘awakens’ the correct device. The next data sequences will only be ‘looked at’ by this device on the bus.

What is the address of an I2C device? The address of any device is usually (but not always) made up of 2 parts. First part is a set of internal address bits. The second part is made up of hardwired address bits using the pins of the IC. So for example, on page 12 of 16 of the MAX6633 datasheet, you can see a table of available addresses to choose from. Notice all the addresses start with 100. This means the internal address bits for all MAX6633 devices are 100. The rest of the 7-bit address is determined by what address pins are wired to either ground (GND) or power (VCC).

So for our example, we are using 0x80. This gives us a 7-bit binary number consisting of ‘100’ internal and A0, A1, A2, A3 pins external all tied to ground.

Another example would be 0x82. In this case, the internal address bits would be ‘100’, the external A3, A2, A1 pins be connected to ground and A0 pin be connected to power.

So where’s the 8th bit? What is the 8th bit? Good question. After every address byte sent by the PIC using I2C, the 8th bit determines if the next byte is written or read by the PIC.

Therefore, in our example we are interested in accessing the CONFIGURATION register.

#include <i2c.h>
int main()
{
   TRISC = 0x00; // turn on tri-state register and
                 // make all output pins
   PORTC = 0x00; // make all output pins LOW
 
   OpenI2C( MASTER, SLEW_OFF);
   SSPADD = 0x3F;</code>
 
   StartI2C(); // begin I2C communication
       IdleI2C();
   WriteI2C( 0x80 ); // sends address to the
                     // device
       IdleI2C();
   WriteI2C( 0x01 ); // sends a control byte to
                     // the device
       IdleI2C();
}

The least significant bit (8th bit) of 0x80 is LOW. Therefore, the PIC is writing the next byte. What is the next byte? It is 0x01.

According to page 13 of 16 of the MAX6633 datasheet, a table shows which registers are located in the IC. Each register has a hex value associated with it. Notice that the configuration register is 01h (or 0x01). Therefore, to access the register, the PIC writes 0x01 as shown above.

Now that we have addressed the correct device and addressed the correct register, it’s time to write something into this CONFIGURATION register.

According to page 13 of 16 of the MAX6633 datasheet, a table shows all the bits of the CONFIGURATION register and explains each one. Every bit of this register will change (configure) the behavior of this device in some way. For our example, we will use 0x20 as the byte to store in this register. You can set this differently later.

#include <i2c.h>
int main()
{
   TRISC = 0x00; // turn on tri-state register and
                 // make all output pins
   PORTC = 0x00; // make all output pins LOW
   OpenI2C( MASTER, SLEW_OFF);
   SSPADD = 0x3F;
 
   StartI2C(); // begin I2C communication
        IdleI2C();
   WriteI2C( 0x80 ); // sends address to the
                     // device
       IdleI2C();
   WriteI2C( 0x01 ); // sends a control byte to
                     // the device
       IdleI2C();
   WriteI2C( 0x20 ); // sends configuration byte –
                     // continuous conversion, 9
                     // bit res
       IdleI2C();
   StopI2C();
}

Now we are done with the configuration. Last thing to do is run StopI2C() and we’re finished. This code only has to be run once per device. Therefore, more than one temp sensor? Then must repeat this for all MAX6633 devices.

BTW, it is also possible to read this register at any time. For now, we’ll move on.

Read Temperature to PIC

First thing we need to do is have a place to put the temperature bytes in our code. I created 2 variables.

char temperatureHI = 0;
char temperatureLO = 0;

These ‘chars’ will contain 8-bit values when together, will represent a 12-bit temperature value. Since a single 8-bit register won’t hold 12-bits, we need 2 of these variable to hold both parts.

If you examine page 13 of 16 of the MAX6633 datasheet, a table shows where the temperature value will be stored (D5 through D14). Without getting into a lengthy discussion on temperature accuracy and bit shifting, we will read 2 bytes (all 16bits) into the PIC however only analyze the upper portion (temperatureHI). Why? Well, for this example, it’s all the accuracy you will need without building a lot of complex bit shifting code. Notice D15 is the sign bit allowing for signed values.

Let’s begin with StartI2C().

(I’ve removed our previous code from the following examples below. Be sure to continue this code after the configuration code)

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
}

Next, we want to address the device again. Notice the 8th bit of 0x80 is LOW.

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
}

According to page 13 of 16 of the MAX6633 datasheet, the TEMPERATURE register is located at address 00h (0x00). So, let’s access it.

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
    WriteI2C( 0x00 );
        IdleI2C();
}

Next, initiate a restart.

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
    WriteI2C( 0x00 );
        IdleI2C();
    RestartI2C(); // Initiate a RESTART command
        IdleI2C();
}

Now, we’re going to read the temperature of the sensor. Notice the 8th bit of the address is now HIGH.

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
    WriteI2C( 0x00 );
        IdleI2C();
    RestartI2C(); // Initiate a RESTART command
        IdleI2C();
    WriteI2C( 0x81 ); // address device w/read
        IdleI2C();
}

At this stage, the PIC will begin to pulse the clock line as usual, however, the MAX6633 will take over the data line and begin sending data to the PIC’s I2C receive register. When done, the value will be stored in temperatureHI. When done, it’s the PIC’s turn to acknowledge (ACK) the device, letting the device know it has received the data. This ACK tells the device the PIC is prepared to receive a second byte.

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
    WriteI2C( 0x00 );
        IdleI2C();
    RestartI2C(); // Initiate a RESTART command
        IdleI2C();
    WriteI2C( 0x81 ); // address device w/read
        IdleI2C();
    temperatureHI = ReadI2C(); // Returns the MSB byte
                               // and stores it in
                               // 'temperatureHI'
        IdleI2C();
    AckI2C(); // Send back Acknowledge
        IdleI2C();
}

Since the MAX6633 device is made to send ALL 16 bits, we must run another ReadI2C command again. What I do is store the second set of data into another variable called temperatureLO although I’m not going to use it. I only care about the value in temperatureHI.

When done, the PIC sends a Not-Acknowledge (NotAck) to tell the device to stop sending data. Finish with a Stop command.

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
     ... // (previous configuration code here)
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
    WriteI2C( 0x00 );
        IdleI2C();
    RestartI2C(); // Initiate a RESTART command
        IdleI2C();
    WriteI2C( 0x81 ); // address device w/read
        IdleI2C();
    temperatureHI = ReadI2C(); // Returns the MSB byte
                               // and stores it in
                               // 'temperatureHI'
        IdleI2C();
    AckI2C(); // Send back Acknowledge
        IdleI2C();
    temperatureLO = ReadI2C(); // returns the LSB of
                               // the temperature
        IdleI2C1);
    NotAckI2C();
        IdleI2C();
    StopI2C();
}

Finally, you have configured the device and have received a temperature (with +/- sign) into a variable called ‘temperatureHI’.

Entire Code

#include <i2c.h>
int main()
{
    char temperatureHI = 0;
    char temperatureLO = 0;
 
   TRISC = 0x00; // turn on tri-state register and
                 // make all output pins
   PORTC = 0x00; // make all output pins LOW
 
   OpenI2C( MASTER, SLEW_OFF);
   SSPADD = 0x3F;
 
   StartI2C(); // begin I2C communication
       IdleI2C();
   WriteI2C( 0x80 ); // sends address to the
                     // device
       IdleI2C();
   WriteI2C( 0x01 ); // sends a control byte to
                     // the device
       IdleI2C();
   WriteI2C( 0x20 ); // sends configuration byte –
                     // continuous conversion, 9
                     // bit res
       IdleI2C();
    StopI2C();
 
    StartI2C();
        IdleI2C();
    WriteI2C( 0x80 );
        IdleI2C();
    WriteI2C( 0x00 );
        IdleI2C();
    RestartI2C(); // Initiate a RESTART command
        IdleI2C();
    WriteI2C( 0x81 ); // address device w/read
       IdleI2C();
    temperatureHI = ReadI2C(); // Returns the MSB byte
                               // and stores it in
                               // 'temperatureHI'
        IdleI2C();
    AckI2C(); // Send back Acknowledge
        IdleI2C();
    temperatureLO = ReadI2C(); // returns the LSB of
                               // the temperature
        IdleI2C1);
    NotAckI2C();
        IdleI2C();
    StopI2C();
}
duty free alcohol airport duty free cigs uk buy duty free cuban cigars where to buy cosmetics duty free fragrances buy tobacco duty free